0. 前言
Promise是我们经常用来管理和简化异步操作的对象,面试中我们经常会被问到相关的问题,比如结合事件循环机制来回答某一段代码的输出顺序,或者要求实现一个异步的任务管理函数,这些都是需要理解 Promise 的原理才能够有底气的回答。还有另一种常见的问题,就是手写 Promise 或者手写 Promise 的各个静态方法。
碰到手撕类的问题,如果我们没有充分准备或者阅读过 Promise 实现的源码,很容易就GG了,有些观点会提到说这种面试题很没有含金量,但是我认为了解如何实现 Promise 对我们的编码还是有很大帮助的,它可以帮助我们更好的理解 Promise 是如何使用统一的状态管理和链式调用机制来帮我们处理复杂任务。
这篇文章会结合 Promise/A+规范 来渐进式地实现我们自己的 Promise 类。
1. 实现 MyPromise 类
我们来分析一下如何使用 Promise :
- 每个 Promise 实例具有三种状态,分别是
Pending
,Fulfilled
,Rejected
- 在使用 Promise 的时候我们会传入一个接收
resolve
,reject
的回调函数,这个回调函数会被同步执行,我们可以在回调函数内部调用入参来修改当前 Promise 实例的状态,同时为了保证 Promise 的可预测性和确定性,我们只能修改一次状态。 - 我们可以使用实例的
then
方法,这个方法接收一个onFulfilled
和onRejected
回调函数用来处理结果或者错误
通过以上分析,我们可以轻松实现一个 Promise 类。
js
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
}
};
// 立即执行我们传入的回调函数
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected){
// 根据当前状态选择执行相应的处理函数
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
我们初步实现了 MyPromise 类,现在用一些代码来测试一下
js
// test1
new MyPromise((resolve, reject) => {
resolve("1");
}).then(
(res) => {
console.log("success", res);
},
(err) => {
console.log("failed", err);
}
);
// test2
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("1");
}, 1000);
}).then(
(res) => {
console.log("success", res);
},
(err) => {
console.log("failed", err);
}
);
我们发现 test1 中控制台会成功输出"success 1",而 test2 则毫无反应,这是因为我们的 then 实现中只处理了状态已经被敲定的情况,而对于 test2 这种状态异步敲定的情况则未做处理,我们可以通过一个数组来暂存传入的处理函数,在状态敲定时去清空暂存数组来实现。
分析完问题我们来修改一下代码。
js
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 定义回调暂存队列
this.onFulfilledCallbackQueue = []
this.onRejectedCallbackQueue = []
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 状态敲定时清空对应的队列
this.onFulfilledCallbackQueue.forEach(fn => fn())
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 状态敲定时清空对应的队列
this.onRejectedCallbackQueue.forEach(fn => fn())
}
};
// 立即执行我们传入的回调函数
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected){
// 根据当前状态选择执行相应的处理函数
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
// 状态为 pending 时暂存回调函数
if (this.status === PENDING) {
this.onFulfilledCallbackQueue.push(() => {
onFulfilled(this.value)
})
this.onRejectedCallbackQueue.push(() => {
onRejected(this.reason)
})
}
}
}
重新测试我们的 test2,我们发现控制台在 1s 后成功打印了内容,到此我们已经实现了一个 Promise 类的基本功能。
2. 链式调用
a. 实现
我们知道,then
方法是支持链式调用的,同时值要在链式调用时往下传递,我们很容易想到一个解决办法:将 then
方法的返回值设置为一个我们的 MyPromise 实例,我们来尝试一下。
js
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 定义回调暂存队列
this.onFulfilledCallbackQueue = []
this.onRejectedCallbackQueue = []
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 状态敲定时清空对应的队列
this.onFulfilledCallbackQueue.forEach(fn => fn())
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 状态敲定时清空对应的队列
this.onRejectedCallbackQueue.forEach(fn => fn())
}
};
// 立即执行我们传入的回调函数
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected){
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
try {
const x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
}
const handleRejected = () => {
try {
const x = onRejected(this.reason)
// onRejected 是用来处理错误的,想象一下我们有这样一个流程:在 promise 敲定后,如果出现错误,在错误处理函数中修正错误使链式调用能够继续
// 所以这里调用的是 resolve 而不是 reject
// fetch('http://bad-url.com')
// .then(
// response => response.json(),
// error => {
// console.error('网络请求失败,使用默认数据:', error);
// return { status: 'offline', data: 'N/A' }; // 恢复 Promise 链
// }
// )
// .then(data => {
// console.log('处理数据:', data);
// });
resolve(x)
} catch (e) {
reject(e)
}
}
// 根据当前状态选择执行相应的处理函数
if (this.status === FULFILLED) {
handleFulfilled()
}
if (this.status === REJECTED) {
handleRejected()
}
// 状态为 pending 时暂存回调函数
if (this.status === PENDING) {
this.onFulfilledCallbackQueue.push(() => {
handleFulfilled()
})
this.onRejectedCallbackQueue.push(() => {
handleRejected()
})
}
})
return promise2
}
}
我们来测试一下上面的代码
js
// test3
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("结果1");
}, 1000);
})
.then(
(res) => {
console.log("success 1", res);
return "结果2";
},
(err) => {
console.log("failed", err);
return "修复的结果2"
}
)
.then(
(res) => {
console.log("success 2", res);
},
(err) => {
console.log("failed", err);
}
);
// test4
new MyPromise((resolve, reject) => {
setTimeout(() => {
reject("结果1");
}, 1000);
})
.then(
(res) => {
console.log("success 1", res);
return "结果2";
},
(err) => {
console.log("failed", err);
return "修复的结果2"
}
)
.then(
(res) => {
console.log("success 2", res);
},
(err) => {
console.log("failed", err);
}
);
我们运行测试代码,发现输出和我们预期的一样,这样就解决了.then
的链式调用......了吗?
b. 问题
想象一个场景,第一个 Promise 中我们处理的是 用户登录请求
,然后第一个 then
中我们根据前面的请求响应的 用户ID
来向服务端请求 用户详细信息
,第二个 then
中我们根据请求到的详细信息来修改 UI 状态。
js
// test5
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("登录信息");
}, 1000);
})
.then(
(userId) => {
// 根据登录得到的id来请求用户信息
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("用户信息");
}, 1000);
})
},
(err) => {
console.log("failed", err);
}
)
.then(
(userInfo) => {
console.log("根据获得的结果修改 UI,结果:", userInfo);
},
(err) => {
console.log("failed", err);
}
);
我们运行 test5 这段测试代码,发现最后控制台打印出来的结果如下
json
根据获得的结果修改 UI,结果: MyPromise {
status: 'PENDING',
value: undefined,
reason: undefined,
onFulfilledCallbackQueue: [],
onRejectedCallbackQueue: []
}
这显然和我们得到用户信息的预期相去甚远,分析一下测试代码,我们可以发现原因是第一个 then
中返回的是一个新的实例。查阅一下 MDN 对 Promise.then
方法的返回值的的描述,其中第4、5、6点提到了返回新的 Promise 实例的情况。

3. 根据规范实现链式调用
根据以上描述我们可以得知,我们在 then
中返回的实例(代码里的 promise2)是要根据不同的返回值做出不同的处理,那么这中间又会涉及到很多的情况,如果我们刚开始接触相关的知识学习,很难去理清所有的情况。但是! Promise/A+规范 为我们提供了充足的指导,它是一个由实现者制定,为实现者服务的开放的标准,用于实现互操作的JavaScript Promise。
a. then 方法
我们直接找到描述实现then的这一节,参照着规范的描述来修改我们的 then 方法
js
...
then(onFulfilled, onRejected) {
// 2.2.1 Both onFulfilled and onRejected are optional arguments
// 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1
// 2.2.7.4 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
// 这几条规则明确了传入的回调函数是可选的,如果未传入相关参数,我们需要给这回调函数设置默认值来穿透行为
// .then().then().then((res) => console.log(res))
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
onRejected =
typeof onRejected === "function"
? onRejected
: (x) => {
throw x;
};
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
// 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
// 这条规定了传入 then 的回调函数应该被异步执行,我们这里使用 setTimeout 模拟实现
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// 2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
// 这条规定了我们要实现一个处理函数,将回调函数的返回值作为参数处理我们的回调函数
// run [[Resolve]](promise2, x)
} catch (e) {
// 2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
reject(e);
}
}, 0);
};
const handleRejected = () => {
// 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
setTimeout(() => {
try {
const x = onRejected(this.reason);
// run [[Resolve]](promise2, x)
} catch (e) {
reject(e);
}
}, 0);
};
if (this.status === FULFILLED) {
handleFulfilled();
}
if (this.status === REJECTED) {
handleRejected();
}
if (this.status === PENDING) {
// 2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
// 对应着我们的回调暂存队列,队列的性质是先进先出,在实现中我们直接通过 forEach 从前往后遍历
this.onFulfilledStack.push(() => {
handleFulfilled();
});
// 2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
this.onRejectedStack.push(() => {
handleRejected();
});
}
});
// 2.2.7 then must return a promise
return promise2;
}
通过阅读规则,我们知道了需要实现一个 Promise 解决程序来处理回调函数的返回值,接下来我们就根据规范来实现这个函数。
b. Promise 解决程序
查阅规范我们得知,"Promise 解决程序"是一项抽象操作,它接受一个 Promise 和一个值 x
作为输入,表示为 [[Resolve]](promise, x)
。如果 x
是一个 thenable
对象,该程序会尝试让 promise
采用 x
的状态,前提是 x
的行为至少在某种程度上类似于一个 Promise。否则,它将以值 x
来完成(fulfilled)promise
。这种对 thenable
的处理方式,使得不同的 Promise 实现能够互相操作,只要它们都暴露一个符合 Promises/A+ 规范的 then
方法。这也让符合 Promises/A+ 规范的实现,能够'同化'那些行为合理但并不完全遵循规范的实现。
这里实际上就是采用了一个适配器模式,只要 x 实现了规范的 then 方法,则可以被 Promise 链吸收,比如说我们在使用多个第三方库的时候,每个库封装了不同的操作,但是都实现了 then 方法,那么我们就可以在同一个链中无痛使用他们。
我们通过函数来实现这个解决程序。
js
const resolvePromise = (promise, x, resolve, reject) => {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise === x) {
return reject(new TypeError("循环引用"));
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
// const racyThenable = {
// then(resolve, reject) {
// // 同步调用 resolve
// resolve('成功')
// throw new Error('resolve后的异常')
// }
// }
// 不判断 called 的话先被 resolve 然后又会被 catch 捕获调用 reject
let called;
// 2.3.3 Otherwise, if x is an object or function,
if (typeof x === "function" || (typeof x === "object" && x !== null)) {
try {
// 2.3.3.1 Let then be x.then
// 避免 getter 产生副作用
let then = x.then;
// 2.3.3.3
// If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where
if (typeof then === "function") {
then.call(
x,
// 2.3.3.3.1
// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
// 递归调用解决函数
(y) => {
if (called) return;
called = true;
// 递归调用
resolvePromise(promise, y, resolve, reject);
},
// 2.3.3.3.2
// If/when rejectPromise is called with a reason r, reject promise with r
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 2.3.3.4 If then is not a function, fulfill promise with x.
resolve(x);
}
} catch (e) {
// 2.3.3.3.4
// If calling then throws an exception e
// 2.3.3.3.4.1
// If resolvePromise or rejectPromise have been called, ignore it.
// 2.3.3.3.4.2
// Otherwise, reject promise with e as the reason.
// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
// 这两条规范都收敛到同一个 catch 中实现了
if (called) return;
called = true;
reject(e);
}
} else {
// 2.3.4 If x is not an object or function, fulfill promise with x.
resolve(x);
}
};
我们测试一下上面的 test5,执行后发现控制台的打印如下:
json
根据获得的结果修改 UI,结果: 用户信息
得到的结果符合我们的预期,如果要简化的理解 resolvePromise
的作用,我认为它起到的作用就是从thenable
对象中解包我们真正需要的返回值。
到此我们实现了 Promise 的核心功能,我们可以通过promises-aplus-tests
库来验证一下我们的 Promise 是否符合规范。
完整代码如下
js
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
const resolvePromise = (promise, x, resolve, reject) => {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise === x) {
return reject(new TypeError("循环引用"));
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
// const racyThenable = {
// then(resolve, reject) {
// // 同步调用 resolve
// resolve('成功')
// throw new Error('resolve后的异常')
// }
// }
// 不判断 called 的话先被 resolve 然后又会被 catch 捕获调用 reject
let called;
// 2.3.3 Otherwise, if x is an object or function,
if (typeof x === "function" || (typeof x === "object" && x !== null)) {
try {
// 2.3.3.1 Let then be x.then
// 避免 getter 产生副作用
let then = x.then;
// 2.3.3.3
// If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where
if (typeof then === "function") {
then.call(
x,
// 2.3.3.3.1
// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
(y) => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 2.3.3.3.2
// If/when rejectPromise is called with a reason r, reject promise with r
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 2.3.3.4 If then is not a function, fulfill promise with x.
resolve(x);
}
} catch (e) {
// 2.3.3.3.4
// If calling then throws an exception e
// 2.3.3.3.4.1
// If resolvePromise or rejectPromise have been called, ignore it.
// 2.3.3.3.4.2
// Otherwise, reject promise with e as the reason.
// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
// 这两个规范都收敛到同一个 catch 中实现了
if (called) return;
called = true;
reject(e);
}
} else {
// 2.3.4 If x is not an object or function, fulfill promise with x.
resolve(x);
}
};
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledStack = [];
this.onRejectedStack = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledStack.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedStack.forEach((fn) => fn());
}
};
// 立即执行 executor
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
// 2.2.7.4 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
onRejected =
typeof onRejected === "function"
? onRejected
: (x) => {
throw x;
};
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
// 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
const handleRejected = () => {
// 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.status === FULFILLED) {
handleFulfilled();
}
if (this.status === REJECTED) {
handleRejected();
}
if (this.status === PENDING) {
this.onFulfilledStack.push(() => {
handleFulfilled();
});
this.onRejectedStack.push(() => {
handleRejected();
});
}
});
// 2.2.7 then must return a promise
return promise2;
}
}
// 测试的代码
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise;
我们在命令行运行 npx promises-aplus-tests 文件路径
,可以看到控制台的输出如下

我们成功通过了所有的用例,证明我们的实现是符合规范的。
4. 小结
通过一些测试用例和查阅规范,我们由浅入深地实现了一个 Promise 类。理解了中间的原理之后,其他的静态方法实现起来也很简单,我们可以参考 MDN 上各个静态方法的定义来实现功能,这里不做赘述。
参考文章: