本文将会根据Promises/A+规范实现一个简易版Promise库,该库实现了原生Promise对象中比较常用的API。该Proimse库完整版代码已发布至个人github,接下来从0开始一步步实现这个简易Promise库
1. 最原始的版本
首先关于promise最基本的特性可以总结为以下几个点:
- 1 Promise是一个构造函数,通过new该构造函数创建promise实例对象,new构造函数的时候传入一个函数executor,它接收两个方法类型的形参resolve和reject
- 2 new Promise得到实例对象后,该对象默认状态为pending。在executor中调用resolve后该对象状态变为fulfilled,调用reject后该对象变为rejected状态
- 3 调用resolve时,必须接收一个value值作为promise实例对象的value且不可更改
- 4 调用reject时,必须接收一个reason值作为promise实例对象的reason且不可更改
- 5 当执行executor函数时如果抛出了error,则该promise实例对象将变为rejected状态,且抛出的error就是该对象的reason
- 6 一旦promise对象从pending变更为fulfilled状态或者rejected状态之后,promise对象的状态不可更改
- 7 promise实例对象有then方法,可接收两个形参onFulfilled和onRejected(一般都是函数),分别对应proimse对象变更为fulfilled状态时需要执行的函数和promise对象变更为rejected状态时需要执行的函数
基于以上特性,我们可以创建出一个最基础的Promise构造函数
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
function Promise(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = value => {
if (this.status === 'PENDING') {
this.value = value;
this.status = FULFILLED;
}
};
const reject = reason => {
if (this.status === 'PENDING') {
this.reason = reason;
this.status = REJECTED;
}
};
try {
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
Promise.prototype.then = function then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
2. 加上异步特性
上面的版本是最基本的版本,仅适用于在new Promise的时候传入的函数executor同步执行resolve或reject方法的场景,接下来要考虑如果executor中使用了发出了ajax等异步调用resolve或reject的场景,如下所示:
let p = new Promise((resolve) => {
// 这里可以使用setTimeout或者发出ajax请求后在回调中调用resolve/reject方法
setTimeout(() => {
resolve(100)
}, 1000)
});
p.then(res => console.log(res));
核心方案其实就是发布订阅模式,将then方法接收到的两个形参先保存起来(订阅),等promise实例对象状态变更时将之前保存的函数拿出来依次执行(发布),所以我们在之前版本上加上发布订阅的逻辑
function Promise(executor) {
const resolve = value => {
if (this.status === 'PENDING') {
this.value = value;
this.status = FULFILLED;
// 发布,将之前保存起来的Fulfilled状态的回调函数拿出来依次执行
if (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
}
}
};
const reject = reason => {
if (this.status === 'PENDING') {
this.reason = reason;
this.status = REJECTED;
// 发布,将之前保存起来的Rejected状态的回调函数拿出来依次执行
if (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.forEach(cb => cb(this.reason));
}
}
};
};
Promise.prototype.then = function then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
// 订阅,当执行then函数时promise状态未变更时,就将两个回调函数先保存起来
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
3. then方法的返回值
经过上面2步后我们的promise已经能支持executor中的异步逻辑了,接下来我们要考虑then方法的返回值 (该返回值可用于链式调用then方法)。我们先来看下PromiseA+中关于then方法的描述
我们对这几个特性做一下解释:
- 2.2.7
then
方法需要返回一个新的promise对象promise2 - 2.2.7.1 如果promise1.then中的onFulfilled和onRejected方法执行时返回一个值x,则需要将该值与需要返回的promise2传入Promise Resolution Procedure方法中,该方法会根据x的值来锕promise2的状态
- 2.2.7.2 执行onFulfilled或onRejected的过程中抛出了异常时,promise2应该变为irejected状态,且该异常为它的reason
- 2.2.7.3 如果promise1是fulfilled状态,但是promise1.then传入的onFulfilled并不是一个函数,则需要返回的promise2将会变成fulfilled状态且它的value值与promise1的value一致
- 2.2.7.4 如果promise1是rejected状态且promise1.then传入的onRejected并不是一个函数,则需要返回的promise2将会变成rejected,且reason值与promise1的reason一致
此外,在Promises/A+规范中还提到了一点(在3.Notes的3.1部分)
3.1 Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such assetTimeout
orsetImmediate
, or with a “micro-task” mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
这段话大致的意思就是说promise.then的回调中传入的onFulfilled和onRejected是需要被异步执行的,比如使用setTimeout
或setImmerdiate
的宏任务或者MutationObserver
和process.nextTick
的微任务来处理这两个函数。所以接下来我们要对上面完成的then方法做一些改造,来实现以上规范中提到的点
Promise.prototype.then = function then(onFulfilled, onRejected) {
// 这个新创建的promise2就是then方法要返回的promise对象
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// 3.1规范 onFulfilled和onRejected被异步执行
setTimeout(() => {
try {
// 2.2.7.1 将onFulfilled的返回值x和promise2传入Promise Resolution Procedure方法(我们定义方法名为resolvePromise)
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
// 2.2.7.2 执行时抛出异常则promise2变为rejected状态,且该异常作为promise2的reason
reject(err);
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
// 2.2.7.1 规范 将onRejected的返回值x和promise2传入Promise Resolution Procedure方法(我们定义方法名为resolvePromise)
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
// 2.2.7.2 执行时抛出异常则promise2变为rejected状态,且该异常作为promise2的reason
reject(err);
}
}, 0);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
}, 0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
})
});
}
});
// 2.2.7 返回一个新的promise对象
return promise2;
}
4. Promise Resolution Procedure
接下来我们就要来编写这个最核心的方法了,该方法决定了调用promise1.then方法后将要返回的这个promise2对象应该是什么状态。
这个方法核心逻辑其实就是根据promise1.then时传入的onFulfilled和onRejected执行的返回值 x 来分别调用promise2的resolve和reject方法来改变promise2的状态。我们来看下PromiseA+是如何解释该方法的
这里简单地概括一下这几条规范说了什么,其中x表示promise1的onFulfilled和onRejected方法执行的返回值,promise2表示执行then方法要返回的新的promise对象
- 当x和promise2指向同一个对象时,抛出一个错误并不再往下执行
- 如果x非对象和函数 (说明是普通值),将promise2置为fulfilled状态且value为x
- 如果x是一个promise对象,如果是pending状态则需要等待其状态变更,因此调用x.then方法,如果执行了onFulfilled的话则将返回的promise2也置为fulfilled状态,并且继承x的value,如果执行了onRejected的话则返回的promise2也置为rejected状态,并且继承x的reason
- 如果执行x.then的过程中抛出了一个异常,则需要将promise2置为rejected状态,且该异常对象作为它的reason
接下来我们来实现这个Promise Resolution Procedure,这里我们将其声明为resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
/**
* 2.3.1 如果promise2和x指向同一个对象,将promise2置为rejected且不需要继续往下执行
* 这个处理是考虑下面这种场景
* let promise1 = new Promise(resolve => resolve(1));
* let promise2 = promise1.then(() => {return promise2})
* 这种逻辑没有任何实际意义,所以我们不需要处理直接报错即可
*/
return reject(new TypeError('Chaining cycle detected for promise'));
}
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
// 2.3.3 x is an object or function 只有是对象或者函数,才可能有then属性,那么它才可能是一个promise
try {
// 2.3.3.1 Let then be x.then
let then = x.then;
if (typeof then === 'function') {
// 2.3.3.3 if then is a function,call it with x as this, first argument resolvePromise, and second argument rejectPromise
// 这里已经满足x是一个对象或函数,它有一个then属性且then属性为函数,则可以认定为它是一个promise对象了,执行then方法
then.call(x, y => {
// 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
// 这里x.then执行onFulfilled方法时,得到的y可能还是一个promise,所以这里需要递归调用resolvePromise以确认这里y是一个promise(是fulfilled还是rejcted)还是一个普通值,从而决定返回的promise2的状态
resolvePromise(promise2, y, resolve, reject);
}, r => {
// 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r
reject(r);
})
} else {
// 2.3.3.4 If then is not a function, fulfill promise with x
// then不是一个函数的话,说明then只是一个普通的属性值,那么x就只是一个普通的对象,此时将promise2置为fulfilled状态
resolve(x);
}
} catch(err) {
// If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
// 取then的过程中抛出异常了,则直接将promise2置为rejected状态
reject(err);
}
} else {
// 2.3.4 if x is not an object or function, fulfill promise with x
// x既不是对象也不是函数,那么肯定是普通值,那直接resolve即可
resolve(x);
}
}
到这一步这个resolvePromise方法的大体逻辑就完成了,这里还有一个细节需要说一下,PromiseA+规范中有这么一条:
2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
这个意思是如果x是一个promise对象并且它的resolve和reject都被调用了,或者说resolve或reject被调用了多次的话,那么只有第一次调用有效,后面的调用都应该被忽略。
我们知道我们自己写的promise在resolve和reject方法中都有一个if (this.status === PENDING)
的判断,也就是如果之前已经调用过一次resolve或reject的话,重复调用时会被这个if语句拦截掉直接return,那么这里为什么还要特别加这么一条规范呢?
因为这里的x除了会是我们实现的promise对象外,还有可能是使用第三方库如bluebird
、Q
创建出来的promise对象,这些第三方库的promise对象,我们是不能保证它会像我们自己的库在resolve和reject方法中加一层判断,所以我们要对这些第三方库的promise对象的行为做处理,即便第三方库的promise重复调用resolve、reject,我们的代码也只会响应一次后续不会再做处理,所以我们需要改造一下resolvePromise方法,增加一个标志位来避免promise对象状态的重复变更
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
// 增加一个flag标识该promise对象的状态是否已经变更过了
let called = false;
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
// 如果状态已经变更过,直接return不做后续处理
if (called) return
called = true;
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return
called = true;
reject(r);
})
} else {
resolve(x);
}
} catch(err) {
if (called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
5. 支持Promise对象的状态透传
上面的代码中还少了一个特性,我们看下promise规范对于.then方法中传递的参数的说明
- 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
- 2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
意思就是说如果promise1是fulfilled状态且调用promise1.then
时中未传递第1个参数或者第1个参数不是一个函数,那么返回的promise2也将变成fulfilled状态,且promise2的value就是promise1的value。如果promise1是rejected状态且调用promise1.then
时未传递第2个参数或者第2个参数不是一个函数,那么返回的promise2将变成rejected状态,且其reason就是promise1的reason。举个例子:
let promise1 = new Promise(resolve => resolve(123));
promise1.then().then().then(res => {
console.log(res); // 结果是123
})
可以理解为当调用then方法没有按规范传入函数形式的参数时,promise的状态是会一直透传下去直到遇到传递了函数参数的then方法。
那么我们应该怎么实现这种功能?其实像上面这种写法是等价于下面这种写法的
let promise1 = new Promise(resolve => resolve(123));
promise1.then(res => res).then(res => res).then(res => {
console.log(res);
})
核心逻辑就是当我们发现then方法传递的参数不是函数时,我们就自己实现函数内容,onFulfilled函数内容只要将它接收到的值(promise1的value)return出去就行了,那么当前产生的新的promise对象在经过resolvePromise方法之后就会将这个return的值作为新promise的value值
还有onRejected也是同样的道理,如果传入的不是函数,那么我们自己实现该函数内容,函数内容就是把前面接收到的内容当做一个Error给throw出去,这样下一个接收的promise就会变成rejected状态且reason为该Error
那么我们在promise.then方法中实现这个逻辑
Promise.prototype.then = function then(onFulfilled, onRejected) {
// 传入的onFulfilled不是函数的话,自己创建一个函数将接收到的value给return出去即可
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
// 传入的onRejected不是函数的话,自己创建一个函数将接收到的内容作为Error直接抛出
onRejected = typeof onRejected === 'function' ? onRejected : (err) => {throw err;}
let promise2 = new Promise(() => {
// ...
});
return promise2;
到这里一个能够实现简单功能的Promise库就已经完成了,此时我们已经可以用它来试验一下了,我们来写个案例:
let p = new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(123);
});
});
let p2 = p.then(res => {
console.log('resolve: ', res);
return 456;
}, err => {
console.log('rejected: ', err);
});
p2.then().then().then().then(res => {
console.log('p2 fulfilled: ', res);
}, err => {
console.log('p2 failed: ', err);
})
最终输出的结果是resolve: 123和p2 fulfilled: 456
到这里Promise库的实现尚未结束,我们还需要使用一个测试工具来检查这个Promise库是否完全符合Promises/A+规范,另外我们还要实现Promise.all
、catch
等API,这些内容会在《根据Promises/A+规范实现Promise(下)》实现