本来我对这种八股是不屑一顾的,什么手写 Promise 手写 DeepEqual 之类的我都不屑一顾。
直到我真的笔试遇到了这种题。
因为没有实现过,笔试的时候也没有足够的时间捋清楚逻辑,所以基本上测试用例只过了几个最标准的,复杂一点的一个都没过。本来觉得这种东西没什么好研究的,后来发现这个东西有专门的测试集,这就有趣起来了,毕竟我还是挺喜欢那种测试用例全通过的感觉的。
幸好这个问题已经有不少人研究过了,有很多现成的源码可以参考。这篇主要记录一下我看代码时的困惑和难点记录。
本 Promise 实现源码参考了 https://jiawei397.github.io/docs/note/js/es6/promisePolyfill.html
如何测试 Promise 实现是否符合标准
https://github.com/promises-aplus/promises-tests
首先 npm 安装一下 promises-aplus-tests ,然后 package.json 里 script 加上
"scripts": {
"test": "run-my-own-tests && promises-aplus-tests test/my-adapter"
}
然后在 MyPromise 类里加上这个方法:
static deferred() {
let result = {};
result.promise = new Promise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports 导出 MyPromise:
module.exports = MyPromise;
npm run test 就可以测试了。
Promise A+ 标准
这是 Promise A+ 的标准。其中包括三个部分。
第一部分

人话:
一个 Promise 有三种状态,pending,fulfuilled,rejected,之间的转换关系如图:

只能由 pending 转为其他两个状态,其余的转换都不允许。
这个特性实现比较简单,只要在 resolve 或者 reject 开头检查一下是不是 pending,如果不是的话直接 return 就可以了。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
status = PENDING;
constructor(fn) {
const resolve = (value) => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
};
const reject = (err) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = err;
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
}
第二部分
第二部分主要描述 then() 函数的实现。这是 Promise 实现最核心的内容。

是不是觉得规范太多看得眼睛都花了?没关系,我们可以一个一个来满足。
首先看 2.2.7,then() 返回一个 promise,所以我们先
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
})
}
实现基本 then
2.2.2 和 2.2.3 基本除了状态不同,要求都是一样的,都是要求当前 promise 状态该为 fulfilled 或者 rejected 后,再去执行 onFulfilled 或者 onRejected,而且 onFulfilled, onRejected 都只能执行一次。
如果当前 promise 已经是 fulfilled 或者 rejected,那只要直接执行就好了,但是如果当前 promise 是 pending 该怎么办呢?或者说,要怎么保证当前 promise 状态改变后才执行 onFulfilled, onRejected 呢?
可以把 onFulfilled 和 onRejected 缓存起来,等到当前 promise 的 resolve 或 reject 被调用后再去调用 onFulfilled 或 onRejected。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
status = PENDING;
// 缓存起来
onFulfilled = undefined;
onRejected = undefined;
constructor(fn) {
const resolve = (value) => {
// ...
this.onFulfilled(this.value);
};
const reject = (err) => {
// ...
this.onRejected(this.reason);
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
})
}
}
new MyPromise((resolve, reject) => {
resolve(2333);
}).then(console.log);
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(999);
}, 100)
}).then(console.log);
new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('Not Legal');
}, 90)
}).then(null, console.log);
这三个简单的测试用例都可以通过,到这里,我们就完成了基本情况。
此时 2.2.1,2.2.2,2.2.3,2.2.5 和 2.2.7(不包括子项)都已经满足了。
onFulfilled 或 onRejected 的特殊情况
多次调用 then
首先看 2.2.6,同一个 promise 可能调用多次 then,此时 then 的内容需要依次执行。
比如说:
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(2333);
}, 100);
})
p.then(console.log);
p.then(console.warn);
p.then((val) => console.log(val + 1));
目前我们的代码只会输出 2334,这明显不符合 2.2.6。
这个问题解决起来也简单,只需要用两个列表来存所有的 onFulfilled 和 onRejected 就行。
class MyPromise {
status = PENDING;
// 缓存起来
onFulfilledList = [];
onRejectedList = [];
constructor(fn) {
const resolve = (value) => {
// ...
this.onFulfilledList.forEach(cb => cb(value));
this.onFulfilledList = [];
};
const reject = (err) => {
// ...
this.onRejectedList.forEach(cb => cb(err));
this.onRejectedList = [];
}
// ...
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else {
this.onFulfilledList.push(onFulfilled);
this.onRejectedList.push(onRejected);
}
})
}
}
这样,刚才的测试代码输出就符合预期了,2.2.6 也满足了。
onFulfilled 或 onRejected 不为函数
2.2.7.3 和 2.2.7.4 要求 onFulfilled 和 onRejected 不为函数时,直接透传当前 promise 内的值。这个实现起来也简单。
感谢博主分享!已入职字节跳动