手写系列作为当下面试中的常青树,如果不能掌握常见的手写方法,怎么能很好的通过面试呢! Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。它有三个状态,分别为pending(等待态)、fulfilled(完成态)和rejectd(拒绝态)。这三个状态的转换关系为pending -> fulfilled、pending -> rejected,即只能由pending态转换到其他态。
Promise的使用方法为:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(12);
}, 1000)
})
promise.then(val => {
console.log(val);
}, reason => {
console.log(reason);
})
从最简单的写起,即传递给Promise的函数是立即执行并有结果产生的,不考虑异步。有如下代码:
class MyPromise {
STATUS = 'PENDING';
val = undefined;
reason = undefined;
constructor(executor) {
executor(this._resolve.bind(this), this._reject.bind(this));
}
_resolve(val) {
// 只有pending状态才能转换到resolved状态
if (this.STATUS !== 'PENDING') {
return;
}
this.STATUS = 'RESOLVED';
this.val = val;
}
_reject(reason) {
// 只有pending状态才能转换到rejected状态
if (this.STATUS !== 'PENDING') {
return;
}
this.STATUS = 'REJECTED';
this.reason = reason;
}
/**
* promise.then的特点
* 1. 接收两个函数,这两个函数都是可选的,第一个函数处理异步任务执行成功的情况,第二个函数处理异步任务执行失败的情况
* 2. then返回的是一个新的Promise对象,因此.then可以链式调用
* 3. 后面的.then如果需要处理异步任务返回的结果,则需要前面的then将该结果return出去
*/
then(onFulFilled, onRejected) {
if (typeof onFulFilled !== 'function') {
return new MyPromise((resolve, reject) => {
resolve(this.val);
})
}
if (this.STATUS === 'RESOLVED') {
return new MyPromise((resolve, reject) => {
const res = onFulFilled(this.val);
resolve(res);
})
}
if (this.STATUS === 'REJECTED') {
return new MyPromise((resolve, reject) => {
if (typeof onRejected !== 'function') {
reject(this.reason);
return;
}
const reason = onRejected(this.reason);
resolve(reason);
})
}
}
catch(onCatch) {
onCatch(this.reason);
}
}
测试代码:
const promise = new MyPromise((resolve, reject) => {
resolve(1234)
});
const temp = promise.then().then().then(res => {
console.log('then3: ', res);
}).catch(reason => {
console.log('catch: ', reason);
})
经过测试,能正常打印内容。但是如果加上异步函数在里面,则打印的内容就不正确了。而在使用Promise的场景中,异步应该是占绝大多数的,因此要加上处理异步的逻辑。
写完上面的代码,本以为对手写Promise自己还是掌握的不少了,但是在后续的测试中发现还有很多情况下执行的结果和预期的不符。在then方法中也只处理了resolved和rejected的情况,对于pending的情况没有做处理,于是,看了在MDN上Promise的介绍和其他大神的文章,在确保自己已经理解清楚后,有如下改版代码:
class MyPromise {
STATUS = 'PENDING';
val = undefined;
reason = undefined;
onFullFilledCallbacks = [];
onRejectedCallbacks = [];
constructor(fn) {
fn(this._resolve.bind(this), this._reject.bind(this))
}
_resolve(val) {
// 只有pending状态才能转换到resolved状态
if (this.STATUS !== 'PENDING') {
return;
}
setTimeout(() => {
this.STATUS = 'RESOLVED';
this.val = val;
this.onFullFilledCallbacks.forEach(callback => {
callback(val);
})
})
}
_reject(reason) {
// 只有pending状态才能转换到rejected状态
if (this.STATUS !== 'PENDING') {
return;
}
setTimeout(() => {
this.STATUS = 'REJECTED';
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => {
callback(reason);
})
})
}
/**
* promise.then的特点
* 1. 接收两个函数,这两个函数都是可选的,第一个函数处理异步任务执行成功的情况,第二个函数处理异步任务执行失败的情况
* 2. then返回的是一个新的Promise对象,因此.then可以链式调用
* 3. 后面的.then如果需要处理异步任务返回的结果,则需要前面的then将该结果return出去
*/
then(onFulFilled, onRejected) {
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
}
if (this.STATUS === 'PENDING') {
this.onFullFilledCallbacks.push(onFulFilled);
this.onRejectedCallbacks.push(onRejected);
}
if (this.STATUS === 'RESOLVED') {
return new MyPromise((resolve, reject) => {
const res = onFulFilled(this.val);
resolve(res);
})
}
if (this.STATUS === 'REJECTED') {
return new MyPromise((resolve, reject) => {
if (typeof onRejected !== 'function') {
reject(this.reason);
return;
}
const reason = onRejected(this.reason);
resolve(reason);
})
}
}
catch(onCatch) {
onCatch(this.reason);
}
}
测试代码如下:
console.log('start');
const promise = new MyPromise((resolve, reject) => {
console.log(123);
resolve(1234);
console.log('3456')
});
const temp = promise.then(res => {
console.log('then1: ', res);
})
const temp2 = promise.then(res => {
console.log('then2: ', res);
})
const temp3 = promise.then(undefined, reason => {
console.log('rejected: ', reason);
})
console.log('end');
打印也是符合预期的。经过如上代码,对于基本的情况都是通过的,但是还有一个很重要的情况没有考虑,那就是then的链式调用。对于then链式调用的逻辑之前一直以为的是只要返回的是新的Promise,那就自然的可以链式调用了。但是在看了一个大神的解析Promise的文章后,深受启发,还有Promise/A+等知识点没有完全理透,因此后续的代码留待掌握了这块知识点后再完善。