Promises/A+ 规范
作为 Modern JavaScript 基础设施的一部分,Promises 对前端开发者而言异常重要。
它是 async/await 语法的基础,是 JavaScript 中处理异步的标准形式。并且,未来的 Web API,只要是异步的,都会以 Promises 的形式出现。
如果不理解 Promises 相关的知识和运行机制,将来可能无法完成 Web 开发的日常工作。
因此,Promises 成为了前端面试中的必问问题之一。在网络上,也可以搜索到很多 Promises 的技术文章。甚至一些前端付费教程,以教你从零实现 Promises/A+ 规范作为卖点。
对于已经理解 Promises 的开发者来说,实现 Promises/A+ 是一个有益的训练。
前期工作
An open standard for sound, interoperable JavaScript promises
了解术语
1.1 “promise” is an object or function with a then method whose behavior conforms to this specification.
1.2 “thenable” is an object or function that defines a then method.
1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise).
1.4 “exception” is a value that is thrown using the throw statement.
1.5 “reason” is a value that indicates why a promise was rejected.
规范的第一部分,描述了几个术语的意思。
promise 是一个包含 then 方法的对象或函数,该方法符合规范指定的行为。
thenable 是一个包含 then 方法和对象或者函数。
value 就是任意合法 JS 值。
exception 就是 throw 语句抛出的值。
reason 是一个指示 promise 为什么被 rejected 的值。
Promise状态
A promise must be in one of three states: pending, fulfilled, or rejected.
2.1 When pending, a promise:
2.1.1 may transition to either the fulfilled or rejected state.
2.2 When fulfilled, a promise:
2.2.1 must not transition to any other state.
2.2.2 must have a value, which must not change.
2.3 When rejected, a promise:
2.3.1 must not transition to any other state.
2.3.1 must have a reason, which must not change.
promise 有 3 个状态,分别是 pending, fulfilled 和 rejected。
在 pending 状态,promise 可以切换到 fulfilled 或 rejected。
在 fulfilled 状态,不能迁移到其它状态,必须有个不可变的 value。
在 rejected 状态,不能迁移到其它状态,必须有个不可变的 reason。
Then方法
A promise must provide a then method to access its current or eventual value or reason.
A promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected)
promise 必须有 then 方法,接受 onFulfilled 和 onRejected 参数。
onFulfilled 的参数是 value,onRejected 函数的参数是 reason。
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
class Promise {
constructor(executor) { // 执行器
this.status = PENDING
this.value = undefined
this.reason = undefined
let resolved = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = RESOLVED
}
}
let rejected = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
}
}
try {
executor(resolved, rejected)
} catch (e) {
rejected(e)
}
}
then(onFulfilled, onRejected) {
if (this.status === RESOLVED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
处理异步情况
class Promise {
constructor {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = RESOLVED;
this.onFulfilledCallbacks.forEach(fn => fn()); // 订阅
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status === RESOLVED) {
// 根据x的值来判断promise2的状态
setTimeout(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}, 0);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
// 处理异步的情况
if (this.status === PENDING) { //发布订阅模式
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
})
return promise2;
}
}
The Promise Resolution Procedure
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pQiIDhqv-1614052331001)(…/…/.vuepress/public/img/promise.jpg)]
第一步,如果 result 是当前 promise 本身,就抛出 TypeError 错误。
第二步,如果 result 是另一个 promise,那么沿用它的 state 和 result 状态。
第三步,如果 result 是一个 thenable 对象。先取 then 函数,再 call then 函数,重新进入 The Promise Resolution Procedure 过程。
const resolvePromise = (promise2, x, resolve, reject) => {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>]'));
}
if(typeof x === 'object' && x !== 'null' || typeof x === 'function') {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
resolve(y);
}, r => {
reject(r);
})
}
} catch (e) {
reject(e);
}
let then = x.then;
} else {
resolve(x);
}
}
总结
能手写 promises 实现,对精通 promises 帮助不大。Promise/A+ 规范,重点围绕的是 How to implement。而不是 How to use。
想要精通 promises,还得在日常开发的各个异步场景中,多加思考和训练。