前言
遥想当年,自学前端(零星学习 6 个月+集中学习 4 个月)后,开始准备面试。在一个交流群里看到一位同学面试让手写 Promise。当时我对 Promise 的理解还仅限于读过阮一峰老师的《ES6 入门教程》,基本没有一点使用经历。无知的我在网上找了一个 Promise 实现,战战兢兢的抄写了一遍,没能理解。一年多以后,又要准备面试了,这一次,这块硬骨头终于啃下来啦,超级开心。😊
接下来让我们一起,理解、实现 Promise 吧。(本文仅包括 Promise 的简单实现,Promise 的基本用法参考阮一峰老师的ES6入门教程。)
三种状态
- Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦状态改变,就不会再变。
- Promise 是一个构造函数,接受一个函数 executor 作为参数。
- 函数 executor 的两个参数分别是 resolve 和 reject。。
- resolve 和 reject 都是函数,改变 Promise 的状态,执行then 方法注册的回调
- resolve 将状态从 pending 变为 resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
- reject 将状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
- Promise 实例化时,函数 executor 函数会立即执行,如果执行出错,reject(error)。
const PENDING = "Pending";
const FULFILLED = "Fulfilled";
const REJECTED = "Rejected";
class MyPromise {
status = PENDING;
value;
reason;
successCallbacks = [];
failCallbacks = [];
constructor(executor) {
if (typeof executor !== "function")
throw new TypeError("Promise resolver is not a function at new Promise");
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
};
resolve = (value) => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
this.successCallbacks.forEach((cb) => cb());
};
reject = (reason) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
this.failCallbacks.forEach((cb) => cb());
};
}
实例方法
Promise.prototype.then()
- then 方法接受两个参数,分别是 successCallback 和 failCallback。根据当前状态执行相应的回调。
- successCallback 和 failCallback 这两个参数为可选参数,默认值分别为
v=>v
、e=>{throw e}
,将值或失败理由向后传递;两个回调参数如果执行出错,reject(error)。 - then 方法调用时,返回一个新的 promise。
- 如果状态已经改变(fufilled 或 rejected),执行状态对应的回调。
- 如果状态没有改变,还是 pending(异步操作后才改变状态),需将回调保存,在 resolve 或 reject 执行改变状态后再执行回调。
- 同一个 promise 对象的 then 方法,可以多次调用。
- then 方法可链式调用,下一个 then 方法拿到的是当前 then 方法 return 的值:
- return 当前 promise,循环引用是不被允许的。(由于需要判断循环引用问题,需将 then 方法的回调放到 setTimeout 中做一个延迟执行,才能拿到当前 promise,方便与 return 的 promise做对比)。
- return 新的 promise,调用该 promise 的 then 方法即可。
- return 其他非 promise 的值,resolve(v) 即可。
class MyPromise {
// ...
then(successCallback, failCallback) {
successCallback = successCallback || ((value) => value);
failCallback =
failCallback ||
((reason) => {
throw reason;
});
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// setTimeout 不能提取到 handleThenCallback 中,不然就拿不到 p
setTimeout(() => {
handleThenCallback(p, successCallback, this.value, resolve, reject);
}, 0);
} else if (this.status === REJECTED) {
setTimeout(() => {
handleThenCallback(p, failCallback, this.reason, resolve, reject);
}, 0);
} else {
this.successCallbacks.push(() => {
setTimeout(() => {
handleThenCallback(p, successCallback, this.value, resolve, reject);
}, 0);
});
this.failCallbacks.push(() => {
setTimeout(() => {
handleThenCallback(p, failCallback, this.reason, resolve, reject);
}, 0);
});
}
});
return p;
}
}
/**
* then 方法失败/成功回调的执行、处理
* @param {*} p then 方法返回的新 promise
* @param {*} callback then 方法传入的成功/失败回调函数
* @param {*} data resolve 的 value,或 reject 的 reason
* @param {*} resolve
* @param {*} reject
*/
function handleThenCallback(p, callback, data, resolve, reject) {
try {
const v = callback(data);
handleThenCallbackReturnValue(p, v, resolve, reject);
} catch (error) {
reject(error);
}
}
/**
* 根据 then 方法 成功/失败回调返回值,改变新 promise 的状态
* @param {*} p then 方法返回的新 promise
* @param {*} v 成功或失败回调执行后返回的值
* @param {*} resolve
* @param {*} reject
*/
function handleThenCallbackReturnValue(p, v, resolve, reject) {
if (p === v) {
throw new TypeError("Chaining cycle detected for promise #<Promise>");
}
if (v instanceof MyPromise) v.then(resolve, reject);
else resolve(v);
}
Promise.prototype.catch()
catch 方法是 .then(null, failCallback)或.then(undefined, failCallback) 的别名,用于指定发生错误时的回调函数。
class MyPromise {
catch(failCallback) {
return this.then(undefined, failCallback);
}
}
Promise.prototype.finally() [ES2018]
finally 方法,接收一个普通的回调函数(不管 promise 最后状态如何都会执行该回调),返回 promise(传递 value/reason)
class MyPromise {
finally(callback) {
return this.then(
(value) => MyPromise.resolve(callback()).then(() => value),
(reason) =>
MyPromise.resolve(callback()).then(() => {
throw reason;
})
);
}
}
静态方法(将参数包装成Promise)
Promise.resolve()
resolve 方法把传入的参数,转换成 promise,状态为 fulfilled。
- 参数为 promise,原样返回。
- 参数为 thenable 的对象,包装成 promise,并立即执行 then 方法。
- 参数为其他对象或基础类型值,resolve 的 value 为该参数。
- 没有参数,返回 resolved 状态的 promise。
class MyPromise {
static resolve(value) {
if (value instanceof MyPromise) return value;
else if (value && typeof value.then === "function") {
// thenable 的对象,该如何判断 then 方法是 thenable 呢?
// let thenable = {
// then: (resolve, reject) => resolve(42)
// }
} else {
return new MyPromise((resolve, reject) => {
resolve(value);
});
}
}
}
Promise.reject()
reject 方法把传入的参数,转换成 promise,状态为 rejected。reject 的 reason 为传入的参数(原封不动)。
class MyPromise {
static reject(value) {
return new MyPromise((resolve, reject) => {
reject(value);
});
}
}
静态方法(将多个Promise包装成一个Promise)
Promise.all/race/allSettled/any()
,这四种静态方法,都是将多个 Promise 实例,包装成一个新的 Promise 实例。- 接收一个具有 Iterator 接口的参数,如果参数项不是 promise,会先用 Promise.resolve() 包装。
Promise.all()
- 所有参数项实例的状态都变成 fulfilled,包装实例的状态才会变成 fulfilled。resolve 的 value 为所有 Promise 实例返回值组成的数组。
- 任何参数项实例的状态变成 rejected,包装实例的状态变成 rejected。reject 的 reason 为第一个 reject 实例的返回值。
class MyPromise {
static all(array) {
const real_array = iterator2Array(array);
return new MyPromise((resolve, reject) => {
let result = [],
len = real_array.length;
const addDataToResult = (index, value) => {
result[index] = value;
if (--len <= 0) resolve(result);
};
real_array.forEach((item, index) => {
item = item instanceof MyPromise ? item : MyPromise.resolve(item);
item.then(
(value) => addDataToResult(index, value),
(reason) => reject(reason)
);
});
});
}
}
/**
* 将具有 Iterator 接口转化为数组,如果参数不具有 Iterator 接口,抛出错误
* @param {*} v 待转化的参数
*/
function iterator2Array(v) {
let real_array = [];
try {
for (let i of v) real_array.push(i);
} catch (e) {
throw new Error(e.message);
}
return real_array;
}
Promise.race()
最先改变状态的一项实例,决定了包装实例的最后状态。
class MyPromise {
static race(array) {
const real_array = iterator2Array(array);
return new MyPromise((resolve, reject) => {
real_array.forEach((item, index) => {
item = item instanceof MyPromise ? item : MyPromise.resolve(item);
item.then(
(value) => resolve(value),
(reason) => reject(reason)
);
});
});
}
}
Promise.allSettled() [ES2020]
所有参数项实例都返回结果后,包装实例的状态变成fulfilled。resolve 的 value 为一个数组,数组每一项是状态和返回值构成的对象。
class MyPromise {
static allSettled(array) {
const real_array = iterator2Array(array);
return new MyPromise((resolve, reject) => {
let result = [],
len = real_array.length;
const addDataToResult = (index, value) => {
result[index] = value;
if (--len <= 0) resolve(result);
};
real_array.forEach((item, index) => {
item = item instanceof MyPromise ? item : MyPromise.resolve(item);
item.then(
(value) => addDataToResult(index, { status: "fulfilled", value }),
(reason) => addDataToResult(index, { status: "rejected", reason })
);
});
});
}
}
Promise.any() [ES2021]
- 任何一个参数项实例变成 fulfilled 状态,包装实例变成 fulfilled 状态。
- 所有参数项实例变成 rejected 状态,包装实例变成 rejected 状态。抛出的错误是 AggregateError 实例(多个错误包装在一个错误内)。
class MyPromise {
static any(array) {
const real_array = iterator2Array(array);
return new MyPromise((resolve, reject) => {
let len = real_array.length;
const reasons = [];
const addDataToErr = (index, reason) => {
reasons[index] = reason;
if (--len <= 0) {
const err = new AggregateError(reasons, "All promises were rejected");
reject(err);
}
};
real_array.forEach((item, index) => {
item = item instanceof MyPromise ? item : MyPromise.resolve(item);
item.then(
(value) => resolve(value),
(reason) => addDataToErr(index, reason)
);
});
});
}
}
未解决的问题
- 静态方法 resolve, 参数为 thenable 的对象,不知道如何判断 thenable 的对象?
- throw Error, 不知道如何测试?
完整代码 MyPromise,放到码云上了。还用 mocha + istanbul
写了测试代码。