我们都知道实现异步有四种方法:回调、promise、async await、generator和co。而我们这篇文章主要讲promise的实现,不涉及另外三个方法。然后在编码过程中也会严格遵守promise/A+规范。注意:最终的代码可能与文章中代码略微不同,请以最后的源代码为主。
一、promise的基本实现
首先,我们首先在promise.js中实现promise的以下四个状态。
- 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用
fulfill
来表示解决,但在后世的 promise 实现多以resolve
来指代之。 - 拒绝(reject):指一个 promise 失败时进行的一系列操作。
- 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
- 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。
// promise.js
class Promise {
constructor (executor) {
// 参数校验,只能传递函数
if (typeof executor !== 'function') {
throw(`Promise resolver ${executor} is not a function`);
}
this.initValue();
this.initBind();
executor(this.resolve, this.reject);
}
initValue () {
this.value = null;
this.status = 'pending';
this.reason = null;
}
// 因为测试文件使用了resolve(1),所以要改变this作用域,让其指向Promise实例。否则会报this.status为undefined
initBind () {
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
resolve (value) {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
}
}
reject (reason) {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
}
}
}
module.exports = Promise;
然后在test.js中测试边写的promise代码。
// test.js
let Promise = require('./promise');
new Promise((resolve, reject) => {
console.log('我执行了promise');
resolve(1);
})
二、then方法的实现
一个 promise 必须提供一个 then
方法以访问其当前值、终值和据因。
promise 的 then
方法接受两个参数:
promise.then(onFulfilled, onRejected)
在这里,onFulfilled
和 onRejected
都是可选参数。
- 如果
onFulfilled
不是函数,其必须被忽略 - 如果
onRejected
不是函数,其必须被忽略
实际上,上述说法是有偏颇的。不是被忽略,而是重新包装了一下,传递给下一个then,比如下面这个代码:
new Promise((resolve, reject) => {
resolve(1);
// reject('异常');
})
.then(value => {
console.log('传递过来的值', value); // 实际不会被忽略,而是重新包装了一下,走到了这里
}, reason => {
console.log('传递过来的异常', reason);
})
所以,根据前面已经实现的Promise代码,我们还需要新增then方法。
class Promise {
constructor (executor) {
// 参数校验,只能传递函数
if (typeof executor !== 'function') {
throw(`Promise resolver ${executor} is not a function`);
}
this.initValue();
this.initBind();
executor(this.resolve, this.reject);
}
initValue () {
this.value = null;
this.status = Promise.PENDING;
this.reason = null;
}
// 因为测试文件使用了resolve(1),所以要改变this作用域,让其指向Promise实例。否则会报this.status为undefined
initBind () {
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
resolve (value) {
if (this.status === Promise.PENDING) {
this.status = Promise.RESOLVE;
this.value = value;
}
}
reject (reason) {
if (this.status === Promise.PENDING) {
this.status = Promise.REJECT;
this.reason = reason;
}
}
then (onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => {
return value;
}
}
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw(reason);
}
}
if (this.status === Promise.RESOLVE) {
onFulfilled(this.value);
}
if (this.status === Promise.REJECT) {
onRejected(this.reason);
}
}
}
Promise.PENDING = 'pending';
Promise.RESOLVE = 'fulfilled';
Promise.REJECT = 'rejected';
module.exports = Promise;
then方法实现了,接下来就是让它在异步的场景下也能正常运行。
异步场景一
比如:在浏览器中,输入下面这段代码,控制台将输出:1、2、3、4、这里是resolve的结果: 1。
但是,我们实现的代码却只能按照从上到下的顺序输出:1、2、4、这里是resolve的结果: 1、3。
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve(1);
})
.then(value => {
console.log(4);
console.log('这里是resolve的结果:', value);
}, reason => {
console.log('这里是reject的结果:', reason);
})
console.log(3);
首先,解决这个问题,只需要在onFulfille和onRejected的外层用setTimeout包裹起来即可实现异步。
if (this.status === Promise.RESOLVE) {
setTimeout(() => {
onFulfilled(this.value);
})
}
if (this.status === Promise.REJECT) {
setTimeout(() => {
onRejected(this.reason);
})
}
异步场景二
但是,这时候有另外一个问题,万一像下面这样抛出异常怎么办?我们实现的代码逻辑和正常的promise是不一样的。
console.log(1);
new Promise((resolve, reject) => {
throw new Error('这里是异常'); // 抛出异常怎么办
resolve(1);
})
.then(value => {
console.log(4);
console.log('这里是resolve的结果:', value);
}, reason => {
console.log('这里是reject的结果:', reason);
})
console.log(3);
解决方法也很简单,只需要在 构造函数 调用executor的外面,用try catch做一层保护即可。
constructor (executor) {
// 参数校验,只能传递函数
if (typeof executor !== 'function') {
throw(`Promise resolver ${executor} is not a function`);
}
this.initValue();
this.initBind();
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
异步场景三
Promise实例带有定时器的情况。
console.log(1);
new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('这里是setTimeout');
resolve(1);
});
})
.then(value => {
console.log(4);
console.log('这里是resolve的结果:', value);
}, reason => {
console.log('这里是reject的结果:', reason);
})
console.log(3);
理性的输出结果 | 实际的输出结果 |
---|---|
1 3 这里是setTimeout 4 这里是resolve的结果: 1 | 1 3 这里是setTimeout |
为什么没有走进then方法里面?原因很简单:因为status的状态还未从pending转变为fulfilled或rejected,导致不能执行then方法的onFulfilled和onRejected。
因此,针对该场景,我们需要在initValue()函数中多定义两个变量:onFulfilledCallback和onRejectedCallback。在此问题中,onFulfilledCallback指的是执行setTimeout的resolve(1)时要执行的内部操作,更直白一点的就是上面最后两个if语句中的代码。
initValue () {
this.value = null;
this.status = Promise.PENDING;
this.reason = null;
this.onFulfilledCallback = []; // 异步函数中成功的回调
this.onRejectedCallback = []; // 异步函数中失败的回调
}
然后针对status为pending的情况,在上面截图的底部位置,增加多一段代码:
if (this.status === Promise.PENDING) {
this.onFulfilledCallback.push((value) =>{
setTimeout(() => {
onFulfilled(value)
})
});
this.onRejectedCallback.push((reason) => {
setTimeout(() => {
onRejected(reason)
})
});
}
因为是异步操作,在执行完then()方法的代码后,程序才会调用resolve和reject函数,这时候我们只要调用上面保存的回调函数,即可解决“异步函数setTimeout执行完之后不执行then里面的代码逻辑”的问题。
三、链式调用(简单实现)
then
方法必须返回一个 promise
对象 :
promise2 = promise1.then(onFulfilled, onRejected);
然后我们再看一下promise/A+规范对原型链规范的约束条件。
一句话概括:不论 promise1
被 reject 还是被 resolve , promise2
都会被 resolve,只有前者出现异常时,后者才会被 rejected;否则,两者都只会被resolve。
在原有代码基础上,修改then方法代码,返回一个promise对象给下一个then即可实现链式调用,但是实现的时候也应该注意异常处理。
then (onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => {
return value;
}
}
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw(reason);
}
}
return new Promise((resolve, reject) => {
if (this.status === Promise.PENDING) {
this.onFulfilledCallback.push((value) => {
setTimeout(() => {
try {
resolve(onFulfilled(value));
} catch (e) {
reject(e)
}
})
});
this.onRejectedCallback.push((reason) => {
setTimeout(() => {
try {
resolve(onRejected(reason));
} catch (e) {
reject(e);
}
})
});
}
if (this.status === Promise.RESOLVE) {
setTimeout(() => {
try {
resolve(onFulfilled(this.value));
} catch (e) {
reject(e)
}
})
}
if (this.status === Promise.REJECT) {
setTimeout(() => {
try {
resolve(onRejected(this.reason));
} catch (e) {
reject(e);
}
})
}
})
}
根据上面的代码,我们就实现了promise的多级链式调用。至于验证代码是否正确,根据前面总结的规范,自行验证即可。
四、链式调用(终极实现)
到这一步,距离我们完成Promise代码的书写快接近尾声了。但是,在上述then方法的实现过程中,仍然是有缺陷的。主要是有以下三点没有考虑周全。
x与 promise相等
如果 promise
和 x
指向同一对象,以 TypeError
为据因拒绝执行 promise
。
比如下面这段代码,会报错:UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise
let p1 = new Promise((resolve, reject)=>{
resolve(1)
});
let p2 = p1.then(() => {
return p2;
})
所以,我们还要对第三部分then方法中try…catch()部分的代码进行改造,在调用resolve函数之前,先检验其包裹的回调函数是否包含promise。在这里我们定义了Promise.resolvePromise对其进行校验。
Promise.resolvePromise = function (newPromise, cb, resolve, reject) {
if (newPromise === cb) {
reject(new TypeError('Chaining cycle detected for promise'));
}
}
x 为 Promise
如果在then的onFulfilled方法返回一个promise,按照我们现在的写法是不能得到正确的结果的。
new Promise((resolve, reject) => {
resolve(1)
})
.then(value => {
return new Promise((resolve, reject)=>{
resolve(1);
})
}, reason => {
}).then(value => {
console.log('第二个then的resolve:'+value);
}, reason => {
})
比如上面这段代码就只会返回:// 第二个then的resolve:[object Object]。因此,按照promise/A+的规范:
x
为 Promise如果
x
为 Promise ,则使promise
接受x
的状态 :
- 如果
x
处于等待态,promise
需保持为等待态直至x
被执行或拒绝- 如果
x
处于执行态,用相同的值执行promise
- 如果
x
处于拒绝态,用相同的据因拒绝promise
我们在前面的Promise.resolvePromise方法里继续添加代码:
if (x instanceof Promise) {
if (x === Promise.RESOLVE) {
x.then(value => {
resolve(value)
}, reason => {
reject(reason)
})
}
}
上面的代码看似解决了问题,但是如果存在疯狂套娃的情况呢?比如:
new Promise((resolve, reject) => {
resolve(1)
})
.then(value => {
return new Promise((resolve, reject)=>{
resolve(new Promise((resolve2, reject2) => {
resolve2(2)
}));
})
}, reason => {
}).then(value => {
console.log('第二个then的resolve:'+value);
}, reason => {
})
所以针对该问题的代码最终得修改为:
if (x instanceof Promise) {
x.then(value => {
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
reject(reason);
})
}
x为对象或函数
还有最后一种场景,规范如下:
实现代码如下:
else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
if (typeof x.then === 'function') {
x.then(value => {
if (called) return;
called = true;
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
if (called) return;
called = true;
reject(reason);
})
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
有几个点要说明一下:
- 之所以用
try...catch
是因为x.then
或者其他点操作可能像下面这样定义的,有可能报错。
- 使用了
if (called) return;called = true;
是因为上述规范3.3写的:如果resolvePromise
和rejectPromise
均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用。
最后献上完整的Promise.resolvePromise
方法的代码
Promise.resolvePromise = function (newPromise, x, resolve, reject) {
if (newPromise === x) {
reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof Promise) {
x.then(value => {
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
reject(reason);
})
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
if (typeof x.then === 'function') {
x.then(value => {
if (called) return;
called = true;
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
if (called) return;
called = true;
reject(reason);
})
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
五、promises-aplus-tests包测试promise代码
最后,我们再npm install promises-aplus-tests测试promise代码。并且在代码文件底部添加这一段代码
Promise.defer = Promise.deferred = function() {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
如果872条测试用例都跑完了,恭喜你,手撸promise成功。
六、源代码
class Promise {
constructor (executor) {
// 参数校验,只能传递函数
if (typeof executor !== 'function') {
throw(`Promise resolver ${executor} is not a function`);
}
this.initValue();
this.initBind();
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
initValue () {
this.value = null;
this.status = Promise.PENDING;
this.reason = null;
this.onFulfilledCallback = [];
this.onRejectedCallback = [];
}
// 因为测试文件使用了resolve(1),所以要改变this作用域,让其指向Promise实例。否则会报this.status为undefined
initBind () {
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
resolve (value) {
if (this.status === Promise.PENDING) {
this.status = Promise.RESOLVE;
this.value = value;
this.onFulfilledCallback.forEach(fn => fn(this.value)); // 成功的回调
}
}
reject (reason) {
if (this.status === Promise.PENDING) {
this.status = Promise.REJECT;
this.reason = reason;
this.onRejectedCallback.forEach(fn => fn(this.reason)); // 失败的回调
}
}
then (onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => {
return value;
}
}
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw(reason);
}
}
let promise2 = new Promise((resolve, reject) => {
if (this.status === Promise.PENDING) {
this.onFulfilledCallback.push((value) => {
setTimeout(() => {
try {
let x = onFulfilled(value);
Promise.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
});
this.onRejectedCallback.push((reason) => {
setTimeout(() => {
try {
let x = onRejected(reason);
Promise.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
}
if (this.status === Promise.RESOLVE) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
Promise.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
}
if (this.status === Promise.REJECT) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
Promise.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
})
return promise2;
}
}
Promise.PENDING = 'pending';
Promise.RESOLVE = 'fulfilled';
Promise.REJECT = 'rejected';
Promise.resolvePromise = function (newPromise, x, resolve, reject) {
if (newPromise === x) {
reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x instanceof Promise) {
x.then(value => {
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
reject(reason);
})
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let { then } = x;
if (typeof then === 'function') {
then.call(
x,
value => {
if (called) return;
called = true;
Promise.resolvePromise(newPromise, value, resolve, reject);
}, reason => {
if (called) return;
called = true;
reject(reason);
})
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
Promise.defer = Promise.deferred = function() {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise;
参考文献
Promise/A+规范原文: https://promisesaplus.com/
Promise/A+规范译文: http://www.ituring.com.cn/article/66566
手摸手教你实现Promise/A+规范:https://www.bilibili.com/video/BV1L441157jg?p=7
github源代码:https://github.com/dream2023/blog/blob/master/2%E3%80%81promise%E5%8E%9F%E7%90%86/promise.js