本来我对这种八股是不屑一顾的,什么手写 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 内的值。这个实现起来也简单。
感谢博主分享!已入职字节跳动