Promise/A+ 规范
Promise/A+
promise 是一个带有 then 方法的对象或者函数。
promise 表示异步操作的最终结果。与 promise 交互的主要方式是通过其 then 方法,该方法注册回调以接收 promise 的最终值或 promise 无法实现的原因。
本规范详细说明了 then 方法的行为,提供了一个可互操作的基础,所有符合 Promises/A+ 的 promise 实现都可以依赖于该基础来提供。也就是说,所有符合 Promises/A+ 的 promise 实现,他们之间可以相互操作。
要求
promise 状态
一个 promise 必须是以下三种状态其中的一种状态:
- pending(等待)
- fulfilled(完成)
- rejected(拒绝)
一个 promise 的初始状态是“pending”,在“pending”状态下调用 resolve
方法后,其状态变为“fulfilled”,在“pending”状态下调用 reject
方法后,其状态变为“rejected”。
当一个 promise 的状态不是“pending”时,其状态不能再被改变。
then 方法
一个 promise 必须提供一个 then 方法去访问当前或者最终的 value 或者 reason。
一个 promise 的 then 方法接受两个参数,且其返回一个 promise:
1
| Promise.then(onFulfilled?, onRejected?): Promise
|
(1)当 onFulfilled
或 onRejected
不是函数时,应该被忽略。
(2)当 onFulfilled
或 onRejected
是函数时:
- 在 promise 变成 fulfilled 时,调用 onFulfilled, 参数是 value
- 在 promise 变成 rejected 时,调用 onRejected, 参数是 reason
(3)then 返回一个 promise。
1
| promise2 = promise1.then(onFulfilled, onRejected);
|
- onFulfilled 或 onRejected 是函数且返回 x, 那么 promise2 以 x 触发 fulfilled;
- 如果 onFulfilled 或者 onRejected 执行时抛出异常 e, promise2 需要被 reject;
- 如果 onFulfilled 不是一个函数, promise2 以 promise1 的 value 触发 fulfilled;
- 如果 onRejected 不是一个函数, promise2 以 promise1 的 reason 触发 rejected。
(4)onFulfilled 和 onRejected 应放在微任务队列中被调用。
(5)then 方法可以被同一个 promise 调用多次。
Promise/A+ 实现
采用 TS 实现。
1. 声明类型:
1 2 3 4 5 6 7 8 9 10 11 12 13
| type Resolve<T = any> = (value: T) => void; type Reject = (reason: any) => void; type OnFulfilled<T = any, K = any> = (value: T) => K | null | undefined; type OnRejected<K = any> = (reason?: any) => K | null | undefined; type OnFinally = () => void | null | undefined; type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void; type Status = "fulfilled" | "pending" | "rejected"; type Handlers = Array<{ onFulfilled?: OnFulfilled; onRejected?: OnRejected; resolve: Resolve; reject: Reject; }>
|
2. 创建类及构造函数
参考 Promise 的构造函数,其接收一个函数 (resolve, reject) => void
为参数,当执行 resolve(value)
或 reject(reason)
后,PromiseState 由 pending 变为 fulfilled 或 rejected,PromiseResult 由 undefined 变为 value 或 reason。
因此,我们用 status 保存状态,用 result 保存结果。resolve 和 reject 方法的作用就是改变它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class MyPromise<T = unknown> { private readonly FULFILLED = "fulfilled"; private readonly PENDING = "pending"; private readonly REJECTED = "rejected";
private status: Status = this.PENDING; private result: any = undefined;
constructor(executor: Executor<T>) { try { const resolve = (data: T) => { this.changeStatus(this.FULFILLED, data); } const reject = (reason: any) => { this.changeStatus(this.REJECTED, reason); } executor(resolve, reject); } catch(error: any) { this.changeStatus(this.REJECTED, error); }; } private changeStatus(state: Status, result: T) { if (this.status !== this.PENDING) return; this.status = state; this.result = result; } }
|
将状态用常量保存是因为这些状态的名称可能会发生变化,便于维护。
使用 try...catch
是因为 Promise 能捕获到 executor 方法中的语法错误,然后触发 reject。
resolve 和 reject 必须用箭头函数,因为调用 resolve 或 reject 的实际上是 window,不使用箭头函数的话其内部 this 指向 window。
3. 实现 then 方法
在此之前,我们先来考虑何时调用 onFulfilled 和 onRejected。根据规范,当状态变为 fulfilled 时调用 onFulfilled,当状态变为 rejected 时调用 onRejected。那么状态何时改变呢?这里有两种情况(以 onFulfilled 为例):
第一种:
1 2 3
| new Promise(resolve => { resolve(123); }).then(res => {});
|
这种情况是在初始化实例时状态就已经改变,那么在调用 then 方法时应立即调用 onFulfilled。
第二种:
1 2 3 4 5
| new Promise(resolve => { setTimeout(() => { resolve }, 1000); }).then(res => {});
|
这种情况是状态改变发生在调用 then 方法之后,那么应该在 changeStatus 方法中调用 onFulfilled。
此外,then 方法是可以被同一个 promise 多次调用的:
1 2 3 4 5 6 7 8
| const p = new Promise(resolve => { setTimeout(() => { resolve(123); }, 1000); }); p.then(res => {}); p.then(res => {}); p.then(res => {});
|
then 方法是按照调用的顺序执行的,因此,我们需要用数组模拟队列来保存 onFulfilled 和 onRejected,然后在状态改变时调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| class MyPromise<T = unknown> { private readonly FULFILLED = "fulfilled"; private readonly PENDING = "pending"; private readonly REJECTED = "rejected";
private status: Status = this.PENDING; private result: any = undefined; private handlers: Handlers = [];
constructor(executor: Executor<T>) { try { const resolve = (data: T) => { this.changeStatus(this.FULFILLED, data); } const reject = (reason: any) => { this.changeStatus(this.REJECTED, reason); } executor(resolve, reject); } catch(error: any) { this.changeStatus(this.REJECTED, error); }; } private changeStatus(state: Status, result: T) { if (this.status !== this.PENDING) return; this.status = state; this.result = result; this.run(); }
private runOne(resolve: Resolve, reject: Reject, callback?: OnFulfilled | OnRejected) { if (typeof callback === "function") { try { const res = callback(this.result); resolve(res) } catch(error: any) { reject(error); } } else { const settled = this.status === this.FULFILLED? resolve : reject; settled(this.result); } }
private run() { if (this.status === this.PENDING || !this.handlers.length) return;
while (this.handlers.length) { const { onFulfilled, onRejected, resolve, reject } = this.handlers.shift()!; if (this.status === this.FULFILLED) { this.runOne(resolve, reject, onFulfilled); } else { this.runOne(resolve, reject, onRejected); } } }
then<K>(onFulfilled?: OnFulfilled<T, K>, onRejected?: OnRejected<K>) { return new MyPromise<K>((resolve, reject) => { this.handlers.push({ onFulfilled, onRejected, resolve, reject }); this.run(); }); } }
|
如果 onFulfilled 或 onRejected 是函数且返回值是 PromiseLike,其 resolve 的 value 或 reject 的 reason 应该传递到 then 方法返回的 promise 中,可以这样处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private isPromiseLike(value: any) { if (value !== null && (typeof value === "object" || typeof value === "function")) { return typeof value.then === "function"; } return false; }
private runOne(resolve: Resolve, reject: Reject, callback?: OnFulfilled | OnRejected) { if (typeof callback === "function") { try { const res = callback(this.result); if (this.isPromiseLike(res)) { res.then(resolve, reject); } else { resolve(res) } } catch(error: any) { reject(error); } } else { const settled = this.status === this.FULFILLED? resolve : reject; settled(this.result); } }
|
此外,onFulfilled 和 onRejected 方法是异步执行的,这可以通过“宏任务”机制(如 setTimeout 或 setImmediate)或“微任务”机制(如 mutationobserver 或 process.nextick)来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| private microTask(run: () => void) { if (typeof process === "object" && typeof process.nextTick === "function") { process.nextTick(run); }
else if (typeof MutationObserver === "function") { const ob = new MutationObserver(run); const textNode = document.createTextNode("1"); ob.observe(textNode, { characterData: true }); textNode.data = "2"; } else { setTimeout(run, 0); } }
private runMicriTask(resolve: Resolve, reject: Reject, callback?: OnFulfilled | OnRejected) { this.microTask(() => { if (typeof callback === "function") { try { const res = callback(this.result); if (this.isPromiseLike(res)) { res.then(resolve, reject); } else { resolve(res) } } catch(error: any) { reject(error); } } else { const settled = this.status === this.FULFILLED? resolve : reject; settled(this.result); } }); }
|
4. 实现 catch 方法
catch 方法就是没有 onFulfilled 的 then 方法。
基于此准则,catch 方法实现如下:
1 2 3
| catch<K = never>(onRejected?: OnRejected<K>) { return this.then<K>(undefined, onRejected); }
|
5. 实现 finally 方法
finally 方法的作用就是在 promise 拿到结果之后(无论是完成态还是拒绝态)进行一些处理。
1 2 3 4 5 6 7 8 9 10 11 12
| finally(onFinally?: OnFinally) { return this.then<T>( () => { onFinally && onFinally(); return undefined; }, () => { onFinally && onFinally(); return undefined; }, ) }
|
6. 实现 resolve 和 reject 方法
我们可以发现,在使用 Promise 的时候可以直接调用 Promise.resolve()
和 Promise.reject()
方法。然而在其返回的 Promise 对象中却找不到这两个方法。
基于这个特性,可以用静态方法来实现,因为静态成员可以由类名直接调用,但对象却不能调用。
1 2 3 4 5 6 7 8 9 10
| static resolve<K = unknown>(value: K) { return new MyPromise<K>((resolve => { resolve(value); })) } static reject<K = never>(reason?: any) { return new MyPromise<K>((_, reject) => { reject(reason); }); }
|
7. 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
| type Resolve<T = any> = (value: T) => void; type Reject = (reason: any) => void; type OnFulfilled<T = any, K = any> = (value: T) => K | null | undefined; type OnRejected<K = any> = (reason?: any) => K | null | undefined; type OnFinally = () => void | null | undefined; type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void; type Status = "fulfilled" | "pending" | "rejected"; type Handlers = Array<{ onFulfilled?: OnFulfilled; onRejected?: OnRejected; resolve: Resolve; reject: Reject; }>
class MyPromise<T = unknown> { private readonly FULFILLED = "fulfilled"; private readonly PENDING = "pending"; private readonly REJECTED = "rejected";
private status: Status = this.PENDING; private result: any = undefined; private handlers: Handlers = [];
constructor(executor: Executor<T>) { try { const resolve = (data: T) => { this.changeStatus(this.FULFILLED, data); } const reject = (reason: any) => { this.changeStatus(this.REJECTED, reason); } executor(resolve, reject); } catch(error: any) { this.changeStatus(this.REJECTED, error); }; } private changeStatus(state: Status, result: T) { if (this.status !== this.PENDING) return; this.status = state; this.result = result; this.run(); }
private isPromiseLike(value: any) { if (value !== null && (typeof value === "object" || typeof value === "function")) { return typeof value.then === "function"; } return false; }
private microTask(run: () => void) { if (typeof process === "object" && typeof process.nextTick === "function") { process.nextTick(run); }
else if (typeof MutationObserver === "function") { const ob = new MutationObserver(run); const textNode = document.createTextNode("1"); ob.observe(textNode, { characterData: true }); textNode.data = "2"; } else { setTimeout(run, 0); } }
private runMicriTask(resolve: Resolve, reject: Reject, callback?: OnFulfilled | OnRejected) { this.microTask(() => { if (typeof callback === "function") { try { const res = callback(this.result); if (this.isPromiseLike(res)) { res.then(resolve, reject); } else { resolve(res) } } catch(error: any) { reject(error); } } else { const settled = this.status === this.FULFILLED? resolve : reject; settled(this.result); } }); }
private run() { if (this.status === this.PENDING || !this.handlers.length) return;
while (this.handlers.length) { const { onFulfilled, onRejected, resolve, reject } = this.handlers.shift()!; if (this.status === this.FULFILLED) { this.runMicriTask(resolve, reject, onFulfilled); } else { this.runMicriTask(resolve, reject, onRejected); } } }
then<K>(onFulfilled?: OnFulfilled<T, K>, onRejected?: OnRejected<K>) { return new MyPromise<K>((resolve, reject) => { this.handlers.push({ onFulfilled, onRejected, resolve, reject }); this.run(); }); } catch<K = never>(onRejected?: OnRejected<K>) { return this.then<K>(undefined, onRejected); } finally(onFinally?: OnFinally) { return this.then<T>( () => { onFinally && onFinally(); return undefined; }, () => { onFinally && onFinally(); return undefined; }, ) }
static resolve<K = unknown>(value: K) { return new MyPromise<K>((resolve => { resolve(value); })) } static reject<K = never>(reason?: any) { return new MyPromise<K>((_, reject) => { reject(reason); }); } }
|