今天,我们要手写一个 promise,不过,重点并不是那段代码,而是更加深入的理解 promise 的用法。
为什么呢?因为如果把重点放在「手写 promise」这件事上,可能只是应试的时候才受益,并且这样死记下来的写法很容易忘记,但是如果我们深刻的掌握了 promise ,会发现「手写promise」是一件顺其自然、水到渠成的事情。
promise 是 ES 6 引入的语法,它的实现规范按照 Promises/A+。本文就主要参考这个规范来实现我们的 promise。
我们就从研究 Promise 对象开始。
通常,我们会像下面这样构造一个 Promise 对象:
const p = new Promise(function executor(resolve, reject) => {
resolve('hello world')
})
在上面,我们使用 new 操作符新建一个对象,同时构造函数的格式是:
declare function executor(resolve, reject) : void
值得注意的是,每当我们新构造一个 Promise 对象时, executor 函数就会立即执行一遍,因为它是用来设置 promise 内部值的。
在上一篇,我们也提过,一个 promise 对象有三种状态:pending、fulfilled、rejected。
众所周知,我们无法在使用 JS 访问 promise 对象来知道它当前处于哪个状态,promise 的状态是维护在内部的。尽管如此,改变一个 promise 的状态却是我们做的。这也就是 executor 函数的作用。
我们可以在初始化 Promise 对象的时候,调用 resolve 把 promise 的状态置为 fulfilled,也可以调用 reject 把 promise 的状态置为 rejected。
const p = new Promise((resolve, reject) => {
resolve('hello world') // 把 p 改为 fulfilled 态
})
换句话讲,executor 相当于内置的 promise 开放给我们的回调函数,尽管我们无法直接读取 promise 的状态,但是我们通过这个函数与 promise 对象进行沟通。而它的两个参数,我们可以称它们为「置值器」。
这是第一个我们需要改变想法的点。以后要把 Promise 构造函数的入参 executor 看做一个和内置 promise 对象交互的函数。我们经由此函数的两个参数 resolve、reject 来改变 promise 的状态。
接下来我们研究生成的 promise 对象 p。
这个对象也叫做 thenable 对象。什么是 thenable 对象呢?很简单,就是具有 then 方法的函数或者对象,promise 对象就是一个 内置的 thenable 对象,我们也可以马上写一个thenable对象:
const thenableObj = {
then: () => {}
}
对于 p 来说,它有一个函数 then,并且接受两个参数。通常使用的话就是:
p.then(
function onfulfilled() {
console.log('onfulfilled');
},
function onRejected() {
console.log('onRejected');
}
);
then 函数的调用时机就是 p 的状态到了 fulfilled/rejected。 尽管如此,就算 p 的状态最开始就是 fulfilled/rejected ,也不会立马执行。规范里有这么一句话:
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code.
翻译:在 执行上下文 堆栈仅包含平台代码之前,不得调用 onFulfilled
或
onRejected`。
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in whichthen
is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such assetTimeout
orsetImmediate
, or with a “micro-task” mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
翻译:这里的“平台代码”是指引擎、环境和promise实现代码。 在实践中,这个要求确保
onFulfilled
和onRejected
异步执行,在调用then
的事件循环之后,并使用新的堆栈。 这可以通过“宏任务”机制实现,例如setTimeout
或setImmediate
,或使用“微任务”机制,例如MutationObserver
或process.nextTick
。 由于promise实现被认为是平台代码,它本身可能包含一个任务调度队列或“trampoline”,在其中调用处理程序。
也就是说,then 函数的调用肯定是异步的,等 p 到了 fulfilled/rejected,我们就会把当前这个任务加入到异步任务的队列里面去。至于什么时候取出来执行,就是当前没有平台代码(platform code)执行了。也就是我们所说的执行上下文中的同步代码都执行完毕了。
至于 promise 是当做宏任务还是微任务加进去,规范上面并没有做强制要求,不过,浏览器端的实现加的都是微任务。
then 的返回值也是一个 promise 对象,同样有 then 方法。如此往复,可以一直链下去。
p.then().then().then()
如上所见,then 接受的这两个函数都可以省略,如果then 省略了处理当前 promsie 对象状态的函数,原先 promise 的状态会继续往后传递。举个例子来说明这个问题。
我们最开始生成了一个拒绝态的 promsie 对象,但是再紧跟着它的 then 函数里面没有传递 onRejected,那它的状态就被原样的往后传递,直到碰到有 onRejected 处理函数的。
Promise.reject(1)
.then(() => {
console.log('只有成功态'); // 这个不会被打印
})
.then()
.then(undefined, (err) => {
console.log(err); // 1
});
明白了上面这些,已经可以开始动手实现一个初版的 promise 了。作为我们第一版的实现,暂且不关注 then 函数的返回值,因为它的返回值还有很多需要考虑的点,我们就先实现生成一个 promise 对象然后调用它的 then 方法。
事不宜迟,现在就动手。
先写一下我们的调用代码
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('hi')
}, 1000)
})
console.log('a')
p.then((res) => {
console.log('c')
console.log(res);
})
console.log('b')
我们期望的结果是:
a
b
// 一秒后
c
hi
我们就对比着用法来实现代码:
type Executor<T> = (
resolve: (value: T) => void,
reject: (reason?: any) => void
) => void
type ThenCallbackQueueItem<T> = [OnFulfilled<T> | undefined, OnRejected | undefined]
type OnFulfilled<T> = (value: T | undefined) => void;
type OnRejected = (reason: any) => void
class MyPromise<T> {
#status: 'pending' | 'fulfilled' | 'rejected' = 'pending';
#value: T | undefined = undefined;
#reason: any = undefined;
#thenCallbackQueue: ThenCallbackQueueItem<T>[] = [];
constructor(executor: Executor<T>) {
executor(this.resolveFn.bind(this), this.rejectFn.bind(this));
}
// 请暂时忽略 then 的返回值,为了方便,我们先把它写成了 void
// 下一版会改
then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): void {
// promise then 的执行顺序也是按照先挂载的先执行
// 所以我们维护一个队列
this.#thenCallbackQueue.push(
[onFulfilled, onRejected]
);
// 如果调用 then 方法的时候, promise 已经是解决态了
// 那就把当前挂载的所有回调都执行一遍
if (this.isSettled()) {
this.releaseThenCallbackInOrder();
}
}
resolveFn(value: T) {
this.#status = 'fulfilled';
this.#value = value;
// 说明调用 then 的时候 当前 promise 还是 pending 状态
// 那个时候只是把 then 接受的回调函数加入队列里去了
// 我们就在解决了之后再释放队列
if(this.#thenCallbackQueue.length > 0) {
this.releaseThenCallbackInOrder()
}
}
rejectFn(reason: any) {
this.#status = 'rejected';
this.#reason = reason;
if(this.#thenCallbackQueue.length > 0) {
this.releaseThenCallbackInOrder()
}
}
isSettled() {
return this.#status !== 'pending';
}
releaseThenCallbackInOrder() {
// 把执行 then 的操作加入 event loop
setTimeout(() => {
this.#thenCallbackQueue.forEach(([onFulfilled, onRejected]) => {
if (this.#status === 'fulfilled') {
onFulfilled?.(this.#value);
} else {
onRejected?.(this.#reason);
}
});
this.#thenCallbackQueue = [];
});
}
}
使用上面那份实现,我们的输出结果正如我们预想的那样:
之前讨论的时候忽略了它的返回值,现在就先来看一下它返回值到底是什么。
首先需要知道的是,它的类型就是一个 promise 对象,不是什么类似于 promise 的结构(比如 promiseLike 对象)。这就会造成了,当上一个值是非 promise 对象的时候,会被引擎内部,隐式的包装成 promise 对象,再传递下去。
在下面这个示例里:
const p1 = Promise.resolve(1).then(() => {
return 'hi';
});
p1.then(res => {
console.log(res); // hi
});
这里首先使用 Promise.resolve 生成了一个成功状态的 promise,然后调用它的 then 方法,并在它的第一个参数里处理了这个成功状态,返回值记作 p1。接着调用 p1.then,这个结果会打印出 'hi'。
then 函数接受两个参数:onFulfiled、onRejected。也就是说,我们会把这两个函数的返回值作为下一个 promise 的值,在上面那个示例中,其实 p1 就是:
Promsie.resolve('hi')
如果没有返回值呢?
const p1 = Promise.resolve(1).then(() => {});
p1.then(res => {
console.log(res); // undefined
});
这个表现也合理,因为在 JS 中,没有显示的 return 语句,就默认返回 undefined 了。此时如果返回 null,结果就变成了 null 了。
如果我们返回值是一个对象呢?
const obj = {
a: 'hi'
}
const p1 = Promise.resolve(1).then(() => {
return obj;
});
p1.then(res => {
console.log(obj === res); // true
});
这个例子就更直观的反映出了,then 链上下一个 promsie 包装的值,就是上一个 then 函数中某一个处理函数(onFulfiled 或者 onRejected)的返回值,至于是哪一个处理函数的返回值,要看 promise 的状态。
上面讨论的情况都是非 promise 对象,返回的效果就可以想象成使用 Promise.resolve 包装一层,用作下一个 promise。
假设直接返回 promise?就不能想象成用 Promise.resolve 包装了,而是会返回包装了相同值的 promise 对象。
const obj = {
a: 'hi'
};
const x = Promise.resolve(obj);
const p1 = Promise.resolve(1).then(() => {
return x;
});
p1.then(res => {
console.log(res === obj); // true
});
console.log(x === p1); // false 注意这里,和下面 Promise.resolve 的行为会不同
为什么要说 「就不能想象成用 Promise.resolve 包装了」因为 Promsie.resolve 这个 Promise 类的静态方法有些特殊,它会有这样的表现:
const x1 = Promise.resolve(1);
console.log(x1 === Promise.resolve(x1)); // true
它会判断,假如 Promise.resolve 的入参是一个 Promise 实例的时候,就会不加修饰,直接返回。这里的 Promise 实例也包含了继承了 Promise 的类,也就是说,使用 instanceof Promise
判断返回 true 的,就触发这条规则。
继续回到 then 方法返回值的处理,回顾一下:
-
在上面的演示中,说明了如果是非 promise 的值,那就会被包装成一个 promise 对象作为返回值,这个包装的过程可以看做是使用 Promise.resolve 包装
-
如果是 promise,就会产生一个新的包装了相同值的 promise 作为返回值。我们可以用伪代码稍微来模拟一下这个过程:
return new Promise((resolve) => {
上一次的Promise.then((value) => {
resolve(value)
})
好了,它的规则大概就是这样,最后再来看一个特例:PromiseLike。也就是我们上一篇提到的 thenable 对象中排除 Promise 对象。再来复习一下,这次我们直接放规范里的定义:
“promise” is an object or function with a
then
method whose behavior conforms to this specification. “thenable” is an object or function that defines athen
method.
上面我们看到了普通对象的表现,会直接包装后返回,但是如果是 PromiseLike 呢?它会有 "拆包" 的行为:
const obj = {
a: 'hi'
};
const wrapperObj = {
then(resolve: any) {
resolve(obj);
}
};
const p1 = Promise.resolve(1).then(() => {
return wrapperObj;
});
p1.then(res => {
console.log(res === obj); // true
});
上面就是一系列的行为了,种种情况都列举到了,我们就要准备写代码了,现在我们要做的就是把这些情况都整理成测试用例,然后让我们的实现都能跑通上面的例子。
先来看非 thenable 对象。
const EmptyPromise = new MyPromise((resolve) => {
setTimeout(() => {
resolve('hi');
}, 1000);
});
// 不传,返回 undefined
const p0 = EmptyPromise.then(() => {});
p0.then(res => {
console.log(res === undefined);
});
// 原始值
const v1 = Math.random();
const p1 = EmptyPromise.then(() => v1);
p1.then(res => {
console.log(res === v1)
});
// 对象
const v2 = {
value: Math.random()
};
const p2 = EmptyPromise.then(() => v2);
p2.then(res => {
console.log(res === v2);
});
等到真正实现 then 函数具有返回值,我们就发现昨天我们实现的那个版本存在问题,因为昨天的那一版只有在处于 settled 情况下才返回,而 then 要求无论什么情况都要返回。也就是说,返回值是确定的。那怎么实现呢?昨天那一份也不是白做,思路会有一点类似。
最根本的我们要记得上一篇文章强调的,executor 是一个和 Promise 交互的回调函数,我们靠它设置值,既然这样的话,我们可以先返回 promise 对象,但是,在解决了才调用 then 的函数,解决了才往我们的 promsie 里面设置值。
实现如下:
type OnFulfilled<T> = (value: T | undefined) => void;
type OnRejected = (reason: any) => void
export default class MyPromise<T> {
// 省略前面的代码...
then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): MyPromise<any> {
return new MyPromise((resolve) => {
// 增加了第三个参数,会在解决的时候靠它
// 改变当前返回值 promise 的状态
this.#thenCallbackQueue.push(
[onFulfilled, onRejected, resolve]
);
if (this.#isSettled()) {
this.#releaseThenCallbackInOrder();
}
});
}
#releaseThenCallbackInOrder() {
setTimeout(() => {
this.#thenCallbackQueue.forEach(([onFulfilled, onRejected, promiseResolve]) => {
if (this.#status === 'fulfilled') {
const ret = onFulfilled?.(this.#value);
promiseResolve(ret);
} else {
const ret = onRejected?.(this.#reason);
promiseResolve(ret);
}
});
this.#thenCallbackQueue = [];
});
}
}
目前,我们的 then 函数签名如下:
then(onFulfilled?: OnFulfilled<T>, onRejected?: OnRejected): MyPromise<any>
返回值是 any,我们得再更新一下它的类型了,我们已经知道,返回值的结果和 onFulFilled / onRejected 有关,于是,我们把它更新为:
type OnFulfilled<T, T1Result> = (value: T | undefined) => T1Result;
type OnRejected<T2Result> = (reason: any) => T2Result
then<T1Result, T2Result>(onFulfilled?: OnFulfilled<T, T1Result>, onRejected?: OnRejected<T2Result>): MyPromise<T1Result | T2Result> {
return new MyPromise<T1Result | T2Result>((resolve) => {
this.#thenCallbackQueue.push(
[onFulfilled, onRejected, resolve]
);
if (this.#isSettled()) {
this.#releaseThenCallbackInOrder();
}
});
}
接下来讨论 thenable 对象的情况,先讨论其中的 promise 对象。这个就是检测到返回值是 promise 对象,就取出它包装的值,再返回。
这是我们上面的测试用例,稍微了改编了一下:
const obj = {
a: 'hi'
};
const v3 = new MyPromise((resolve) => {
resolve(obj);
});
const p3 = EmptyPromise.then(() => v3);
p3.then((value) => {
console.log(value === obj); // true
});
我们修改的部分就只在 releaseThenCallbackInOrder
:
#releaseThenCallbackInOrder() {
setTimeout(() => {
this.#thenCallbackQueue.forEach(([onFulfilled, onRejected, promiseResolve]) => {
if (this.#status === 'fulfilled') {
const ret = onFulfilled?.(this.#value);
if (ret instanceof MyPromise) {
// onFulfilled 或者 onRejected 可能返回任何状态的 promise
// then 的结果可能是 fulfilled/rejected 之一
// 由于只可能是其中之一,所有只有一个函数走逻辑
// 都兼容一下.
ret.then((res) => {
promiseResolve(res);
}, (err) => {
promiseResolve(err);
});
} else {
promiseResolve(ret);
}
} else {
const ret = onRejected?.(this.#reason);
if (ret instanceof MyPromise) {
ret.then((res) => {
promiseResolve(res);
}, (err) => {
promiseResolve(err);
});
} else {
promiseResolve(ret);
}
}
});
this.#thenCallbackQueue = [];
});
剩下的就是拆包 PromiseLike 对象了。
interface PromiseLike<T> {
then<TResult1, TResult2>(
onFulfilled?: OnFulfilled<T, TResult1>,
onRejected?: OnRejected<TResult2>): Promise<TResult1 | TResult2>;
}
先写一个判断 PromiseLike 的工具方法
#isPromiseLike(obj: unknown): obj is PromiseLike<T> {
if (obj && typeof obj === 'object' && obj.hasOwnProperty('then')) {
let o = obj as PromiseLike<any>;
return o.then && (
typeof o.then === 'function' || typeof o.then === 'object'
);
}
return false;
}
接下来做的事情就简单了,其实处理逻辑和 promise 对象一致,也就意味着,我们只需要更新一下刚才的判断条件即可:
if (ret instanceof MyPromise || this.#isPromiseLike(ret)) {
...
} else {
...
}
用下面这个测试用例测试一下:
const wrapperObj = {
then(resolve: any) {
resolve(obj);
}
};
const p4 = EmptyPromise.then(() => {
return wrapperObj;
});
p4.then(res => {
console.log(res === obj); // true
});
逻辑也是正常,到这里,我们就完成了一个比较完备的 then 函数。
最本质的还是理解它的用法,理解了用法,就可以按照它的用法自己想一下实现。
好了,promise 章节的三篇就完成啦,关于它的一些静态方法,像 Promise.resolve、Promise.reject、Promise.all、Promise.race、Promise.prototype.catch、Promise.prototype.finally,除却 finally ,都比较简单,如果,你有哪一个想了解,可以在下面留言。
谢谢阅读,撒花。
完整代码
type Executor<T> = (
resolve: (value: T) => void,
reject: (reason?: any) => void
) => void
// 改变 promise 的函数,也暂存起来
// 等解决态了,去把他的状态修改掉
type PromiseResolve = any;
// 扩展之前的暂存队列
type ThenCallbackQueueItem<T, T1Result, T2Result> = [
OnFulfilled<T, T1Result> | undefined,
OnRejected<T2Result> | undefined,
PromiseResolve,
]
type OnFulfilled<T, T1Result> = (value: T | undefined) => T1Result;
type OnRejected<T2Result> = (reason: any) => T2Result
interface PromiseLike<T> {
then<TResult1, TResult2>(
onFulfilled?: OnFulfilled<T, TResult1>,
onRejected?: OnRejected<TResult2>): Promise<TResult1 | TResult2>;
}
export default class MyPromise<T> {
#status: 'pending' | 'fulfilled' | 'rejected' = 'pending';
#value: T | undefined = undefined;
#reason: any = undefined;
#thenCallbackQueue: ThenCallbackQueueItem<T, any, any>[] = [];
constructor(executor: Executor<T>) {
executor(this.#resolveFn.bind(this), this.#rejectFn.bind(this));
}
#resolveFn(value: T) {
this.#status = 'fulfilled';
this.#value = value;
// 说明在调用 then 的时候 当前 promise 还是 pending 状态
// 那我们就在解决了之后,再释放一次队列
if (this.#thenCallbackQueue.length > 0) {
this.#releaseThenCallbackInOrder();
}
}
#rejectFn(reason: any) {
this.#status = 'rejected';
this.#reason = reason;
if (this.#thenCallbackQueue.length > 0) {
this.#releaseThenCallbackInOrder();
}
}
#isSettled() {
return this.#status !== 'pending';
}
then<T1Result, T2Result>(onFulfilled?: OnFulfilled<T, T1Result>, onRejected?: OnRejected<T2Result>): MyPromise<T1Result | T2Result> {
return new MyPromise<T1Result | T2Result>((resolve) => {
// 增加了第三个参数,会在解决的时候靠它
// 改变当前返回值 promise 的状态
this.#thenCallbackQueue.push(
[onFulfilled, onRejected, resolve]
);
if (this.#isSettled()) {
this.#releaseThenCallbackInOrder();
}
});
}
#isPromiseLike(obj: unknown): obj is PromiseLike<T> {
if (obj && typeof obj === 'object' && obj.hasOwnProperty('then')) {
let o = obj as PromiseLike<any>;
return o.then && (
typeof o.then === 'function' || typeof o.then === 'object'
);
}
return false;
}
#releaseThenCallbackInOrder() {
setTimeout(() => {
this.#thenCallbackQueue.forEach(([onFulfilled, onRejected, promiseResolve]) => {
if (this.#status === 'fulfilled') {
const ret = onFulfilled?.(this.#value);
if (ret instanceof MyPromise || this.#isPromiseLike(ret)) {
// onFulfilled 或者 onRejected 可能返回任何状态的 promise
// then 的结果可能是 fulfilled/rejected 之一
// 由于只可能是其中之一,所有只有一个函数走逻辑
// 都兼容一下.
ret.then((res) => {
promiseResolve(res);
}, (err) => {
promiseResolve(err);
});
} else {
promiseResolve(ret);
}
} else {
const ret = onRejected?.(this.#reason);
if (ret instanceof MyPromise || this.#isPromiseLike(ret)) {
ret.then((res) => {
promiseResolve(res);
}, (err) => {
promiseResolve(err);
});
} else {
promiseResolve(ret);
}
}
});
this.#thenCallbackQueue = [];
});
}
}
// 下面是测试用例
const EmptyPromise = new MyPromise((resolve) => {
setTimeout(() => {
resolve('hi');
}, 1000);
});
// 0. 不传,返回 undefined
const p0 = EmptyPromise.then(() => {
});
p0.then(res => {
console.log(res === undefined);
});
// 1. 原始值
const v1 = Math.random();
const p1 = EmptyPromise.then(() => v1);
p1.then(res => {
console.log(res === v1);
});
// 2. 对象
const v2 = {
value: Math.random()
};
const p2 = EmptyPromise.then(() => v2);
p2.then(res => {
console.log(res === v2);
});
// 3. promise 对象
const obj = {
a: 'hi'
};
const v3 = new MyPromise((resolve) => {
resolve(obj);
});
const p3 = EmptyPromise.then(() => v3);
p3.then((value) => {
console.log(value === obj); // true
});
// 4. promiseLike 对象
const wrapperObj = {
then(resolve: any) {
resolve(obj);
}
};
const p4 = EmptyPromise.then(() => {
return wrapperObj;
});
p4.then(res => {
console.log(res === obj); // true
});
最后,各位读者,今天是圣诞节,祝你们圣诞快乐。🎄🎄🎄
如果你们有人陪,衷心希望你们过得快乐;如果没人陪,那就收下我送你的这颗圣诞树吧,也希望你终将找到陪你过圣诞的那个他/她。