【手写Promise】-- 深入理解Promise
在此之前,我对于 Promise 有些不求甚解,回顾了我之前写的 手写Promise 这篇文章,发现其错漏百出,我直接 “呵tui!” 。
在写这篇文章之前,我看了很多网上的 “手写Promise” 代码,它们大多有以下的问题:
- 回调的代码(then 方法中的函数参数)执行位置错误。比如我之前写的那篇。(回调的代码应该存入回调队列中,在 resolve 中需要清空一次回调队列;并且在 then 方法中,Promise 状态不为
PENDING
的情况下直接执行回调。) - 为了使 then 方法中传入的函数异步执行,许多代码实现中使用了
setTimeout
对代码进行封装,但是setTimeout
包装的代码是宏任务,而实际上 then 方法中传入的函数是作为微任务被异步执行的,所以使用process.nextTick
将代码包装为微任务应该会更为合理。 - 在实现 all 方法时直接调用 then 方法向回调队列中插入回调事件,这样在 Promise 的执行中,先发生状态改变的 Promise 会首先向结果数组中存入执行结果,这样很可能导致 all 方法接收的 Promise 数组参数与最终返回的结果数组中数据的顺序不匹配(出现竞速)。存在这个错误的比如这个手写实现Promise
刚好今天“前端大全”公众号给我推了一篇介绍 async 的文章,并且有提到一个手写 Promise 的实现,我认为这个是我目前看到的手写实现 Promise 的 polyfill 中比较完善的(应该会比我接下来实现的 polyfill 更为完善),链接如下:
https://github.com/19Qingfeng/notes/blob/master/promise/core/index.js
默认大家此前对 Promise 的使用有了一些基本的了解,接下来我会通过实现 Promise 的 polyfill 代码来作为对于 Promise 理解之后的成果。在实现之前先记录几个便于理解和实现 Promise 的几个注意点。
- Promise 中的状态有 初始状态’PENDING’、结果状态’FULFILLED’和’REJECT’ ,Promise 的状态不受外界影响,只受异步操作的结果决定,并且一旦变更为结果状态,状态就不会再改变了。
- resolve 方法和 reject 方法是内置的用于改变 Promise 状态的函数。
- resolve 方法的参数除了是一个普通的值外,还可能时另一个 Promise。如下所示:
var p1 = new Promise (function (resolve, reject) {
//...
}) ;
var p2 = new Promise (function (resolve, reject) {
//...
resolve(p1);
}) ;
上面的代码中,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作。此时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是Resolved或Rejected,那么p2的对应的回调函数将会立刻执行。
- then 方法会返回一个新的 Promise,基于此实现 Promise 的链式调用。
接下来就来尝试小试一下牛刀:
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const PENDING = 'pending';
// 既然是 polyfill 代码,那么用构造函数实现应该比 class 更加合理
function MyPromise(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.successCallback = []; // resolve 状态的回调队列
this.failureCallback = []; // reject 状态的回调队列
const self = this;
const resolve = (value) => {
// 如果 value 是一个 Promise ,那么就依据 value 的状态来调用相应的回调
if (value instanceof MyPromise) {
// 根据 value 的状态,调用当前 promise 的 resolve 或 reject
return value.then(resolve, reject);
}
// value 为普通值
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.successCallback.forEach(fn => fn(value));
}
}
const reject = (reason) => {
if (self.status === PENDING) {
self.status = REJECTED;
self.reason = reason;
self.failureCallback.forEach(fn => fn(value));
}
}
try {
executor(resolve, reject)
} catch (err) {
reject(err);
}
}
// 实现 then 方法,并支持链式调用(返回一个新的 Promise)
// 因为 onFulfilled(this.value) 的结果(记为 x)会作为下一次 then 方法的 onFulfilled 回调的参数。
// 如果 x 是普通值,直接调用 resolve;如果 x 是一个 PENDING 状态的 Promise,则 x 需保持为等待态直至 x 被执行或拒绝;
// 如果 x 是一个 FULFILLED 或 REJECTED 状态的 Promise,则用同样的方法递归处理 x
// 下面会专门写一个函数 resolvePromise 来判断 x 的值以及状态,并给出相应的处理
MyPromise.prototype.then = function(onFulfilled, onRejected) {
const self = this; // 用 self 保存 this,避免后期闭包导致 this 的指向不对
// 设置默认回调函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : res => res;
onRejected= typeof onFulfilled === 'function' ? onRejected : (err) => { throw err };
let nextPromise = new Promise((resolve, reject) => {
if (self.status === PENDING) {
self.successCallback.push(() => {
process.nextTick(() => {
try {
const x = onFulfilled(self.value);
// 接下来判断这个 x 是不是 promise,以及它的状态; 并依此判断调用 resolve 或 reject 或 继续递归调用 resolvePromise
resolvePromise(nextPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
self.failureCallback.push(() => {
process.nextTick(() => {
try {
const x = onRejected(self.reason);
resolvePromise(nextPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
});
}
if (self.status === FULFILLED) {
process.nextTick(() => {
try {
const x = onFulfilled(self.value);
resolvePromise(nextPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
if (self.status === REJECTED) {
process.nextTick(() => {
try {
const x = onRejected(self.reason);
resolvePromise(nextPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return nextPromise;
}
/* 核心代码 (主要就是为了实现 then 的链式调用)
nextPromise: then 方法返回的新 promise
x: 当前 then 中回调的执行结果,(如果不为 Promise,也是下一次调用 then 的回调的参数)
resolve: 新 promise 的 resolve 方法
reject: 新 promise 的 reject 方法
*/
function resolvePromise(nextPromise, x, resolve, reject) {
// 禁止循环引用
if (nextPromise === x) {
return reject(new TypeError("循环引用!"));
}
// x 是 thenable 时(鸭子类型),只有 object 或 function 类型的数据可能有 then 方法。在 x 为其他类型的情况下,如果直接访问 x.then 的话,是会报类型错误的。这也是我们接下来要进行下一步判断的原因
let called = false; // 这里是为了确保只调用一次 resolve 或 reject 方法。避免用户使用时同时调用这两个方法,或多次调用其中一个方法,而导致的回调队列被多次执行
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then;
// x 是 Promise 或 thenable 对象
if (typeof then === 'function') {
// 如果 x 是Promise, 在ES规范中需要被推迟到下一次loop中去调用then
process.nextTick(() => {
// 为什么这里要在 x 的上下文环境下(即 this 指向 x)调用 then 方法?为了根据 x 的状态,来决定调用 Fulfilled的回调 还是 Reject的回调
then.call(x, (v) => {
if (called) return;
called = true;
resolvePromise(x, v, resolve, reject);
}, (e) => {
if (called) return;
called = true;
reject(e);
})
});
} else {
// x 不是普通类型的值,但是它没有 then 方法,因此不用进行等待,直接 resolve
if (called) return;
called = true;
resolve(x);
}
} catch(err) {
if (called) return;
called = true;
reject(err);
}
} else {
// x 是普通类型的值,就可以直接进行状态更改,不用进行等待
if (called) return;
called = true;
resolve(x);
}
}
接下来实现 静态方法 resolve、reject、all 和 race
MyPromise.resolve = (value) => {
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
MyPromise.reject = (reason) => {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
/* all 方法的实现逻辑
功能:接收一个 Promise 数组,等数组中的每个 Promise 都执行完且都处于 FULFILLED 状态之后,执行 resolve 方法,并将每个 Promise 执行结果构成的数组作为 resolve 的参数。一旦有任意一个 Promise 处于 REJECT 状态就会立刻执行 reject 方法,reject 的参数为这个处于 REJECT 状态的 Promise 的 reject 方法的参数。
逻辑:每个 Promise 状态变为 FULFILLED 状态之后(即调用 resolve 之后),都会向结果数组的相应位置添加一个结果value,并判断数组是否填满(即所有 Promise 是否都已经 FULFILLED 了),一旦数组填满,就会使用这个数组作为参数调用 resolve 方法来执行 then 方法添加的回调。
*/
MyPromise.all = (promises) => {
const values = [];
let count = 0;
return new MyPromise((resolve, reject) => {
if (promises.length === 0){
return resolve(values);
}
promises.map(p => {
// 如果数组成员不是 Promise 实例,就先使用 MyPromise.resolve 对其进行一次包装
return (p instanceof MyPromise) ? p : MyPromise.resolve(p);
}).forEach((promise, i) => {
promise.then((value) => {
values[i] = value;
if (++count === promises.length) {
resolve(values);
}
}, (err) => {
reject(err);
})
});
});
}
MyPromise.race = (promises) => {
return new MyPromise((resolve, reject) => {
if (promises.length === 0){
return resolve();
}
promises.map(p => {
// 如果数组成员不是 Promise 实例,就先使用 MyPromise.resolve 对其进行一次包装
return (p instanceof MyPromise) ? p : MyPromise.resolve(p);
}).forEach((promise, i) => {
promise.then(resolve, reject);
});
});
}
今天在掘金上看到了一篇 Promise.all 的实现相关的文章,也可以稍作参考:
https://juejin.cn/post/7069805387490263047