本文将会根据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,则需要将x与即将返回的promise2传入Promise Resolution Procedure方法中,该方法会根据x的值来改变promise2的状态
- 2.2.7.2 执行onFulfilled或onRejected的过程中抛出了异常时,promise2应该变为rejected状态,且该异常作为它的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
onFulfilledandonRejectedexecute 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 assetTimeoutorsetImmediate, or with a “micro-task” mechanism such asMutationObserverorprocess.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(如果是promise则需要等待其发生状态变更)还是一个普通值,从而决定返回的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 => {
// 如果状态已经变更过,直接return不做后续处理
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(下)》实现