前言
今天太阳明媚在家办公,看了下以前和别人写的 Promise 实现,想重新再梳理一下,就记录了一下这篇文章。
正文
Promise 本质是一个回调任务的控制。数据结构上采用数组和标记量配合实现状态机的变换。
graph TD
A[声明 Promise]
A -->| 初始化为 pending | B(进入 promise 队列)
B --> | 等待通知结束承诺信号 | C{是否 resolve or reject}
C -->|resolved| D[执行 then.resolve]
C -->|rejected| E[执行 then.reject]
可以看出 Promise 中的状态机会有一个明确的 END 节点,如果 END 后无法回退, Promise 这的结构可以看出有以下:
class Promise {
constructor() {
// [[PromiseState]] value
this.state = PENDING;
this.resolvePendingJob = [];
this.rejectPendingJob = [];
}
}
然后在使用 Promise 的时候,会传入一个回调函数,会将 resolve 和 reject 的函数引用传入回调。
new Promise(function callback(resolve, reject) {
// 构造器执行
});
所以在 Promise 初始化的时候,就会执行一次构造器回调:
class Promise {
constructor(execute) {
try {
// 如果这里有异步异常,可能捕获不到
execute();
} catch (e) {
// 如果回调失败了,需要立即变更状态为 REJECT
this.reject(e);
}
}
resolve = (result) => {};
reject = (reason) => {};
}
初始化差不多后,我们需要把依此要执行的逻辑放入 .then 调用链去声明,并且 .then 可以拿到 RESOLVE | REJECT 状态的执行结果,只不过在不同的回调参数位置。
new Promise().then(
function onResolved(result) {
// success
},
function onRejected(reason) {
// fail
}
);
所以我们需要存储一下执行的结果,并且在执行 resolve() | reject() 的时候让状态机流转到新节点。
class Promise {
constructor(execute) {
// [[PromiseResult]]
this.result = null;
try {
// 如果这里有异步异常,可能捕获不到
execute();
} catch (e) {
// 如果回调失败了,需要立即变更状态为 REJECT
this.reject(e);
}
}
runMicroTask(fn) {
// 使用 setTimeout 模拟宿主执行
setTimeout(() => {
fn();
}, 0);
}
then(onResolved, onRejectd) {
// 参数校验
const _onResolved = isFunction(onResolved)
? onResolved
: (result) => result;
const _onReject = isFunction(onReject) ? onReject : (reason) => {
throw new Error(reason);
};
// 具体执行,需要再返回一个 [[Promise]]
return new Promise((resolve, reject) => {
// 声明执行体,等待 resolve, reject 的时候进行执行
const executeResolve = () => {};
const executeReject = () => {};
// 将执行体放入到对应队列中,检测当前状态是否需要变更前一个 Promise 状态
switch (this.state) {
case "RESOLVE":
executeResolve();
break;
case "REJECT":
executeReject();
break;
default:
this.resolvePendingJob.push(executeResolve);
this.rejectPendingJob.push(executeReject);
break;
}
});
}
resolve = (result) => {
if (this.state === PENDING) {
this.runMicroTask(function () {
this.state = RESOLVED;
this.result = result;
this.resolvePendingJob.forEach((resolve) => {
resolve(result);
});
});
}
};
reject = (reason) => {
if (this.state === PENDING) {
this.runMicroTask(function () {
this.state = REJECTED;
this.result = reason;
this.rejectPendingJob.forEach((reject) => {
reject(reason);
});
});
}
};
}
这个时候,基本的 Promise thenable 能力都具备了,那么我们怎么把每一个 .then 给连接起来呢?就需要关注 executeResolve 和 executeReject 的实现。
function isPromise(value) {
if (!value) return false;
// Promise or Promise like
return value instanceof Promise || isFunction(value.then);
}
const executeResolve = () => {
this.runMicroTask(() => {
try {
const value = _onResolve(this.result);
// 支持 Promise 链式返回值继续为 {Promise} 的情况
if (isPromise(value)) {
value.then((res) => {
resolve(res);
});
} else {
resolve(value);
}
} catch (e) {
reject(e);
}
});
};
const executeReject = () => {
this.runMicroTask(() => {
try {
const value = _onReject(this.result);
if (isPromise(value)) {
value.then((res) => {
// 这里比较特殊,如果是 reject 后续只会依此调用 reject
reject(res);
});
} else {
reject(value);
}
} catch (e) {
reject(e);
}
});
};
到这里,其实对 Promise 的执行过程就了解得差不多了。既然简单版本都卷了,就继续看下 ECMAScript 的标准描述吧~首先需要看 25.4 Promise Object
先依此分析一下标准(标准只是一个想规范/统一大家的实现,但是不代表就一定要这么实现,可以当作学习进行了解):
- Promise 它是什么?它是为了设计来解决什么问题?
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
Promise 是一个用来解决延迟(或者异步)取值的对象。
-
Promise 是一个对象,对象就可以有属性和方法。在 JavaScript 中可以泛化理解 class/function/object 都可以叫对象。
-
其次,是用来解决延迟/异步取值问题,由于前端有很多回调的模型,所以通过它来可以更换 callback 调用过深取值,但是它并不能让业务值依赖场景变得简单,这是 2 个概念。然后根据不同的执行上下文,延迟策略是不一样的,在理解上我们通常任务它是一个微任务,跟随 JavaScript EventLoop 机制的执行周期。
所以我们在命名的时候采用了 Job 单词,用于更准确描述它的含义,它并不是一个 FIFO 的队列
- 怎么断言它是一个 Promise?
The abstract operation IsPromise checks for the promise brand on an object.
校验 Promise 对象上的标记。
标准主要认 3 步:
- If Type(x) is not Object, return false.
- If x does not have a [[PromiseState]] internal slot, return false.
- Return true
[[PromiseState]] 我们这里的算法比较简单,通过 2 个条件做判断
-
value instanceof Promise直接通过 instanceof 操作符算法,这个只是写法 -
isFunction(value.then)通过识别拥有 thenable 能力进行判断,可以参考一些标准描述:Promise.prototype.then 和 PerformPromiseThen
// 伪代码
function IsPromise(value) {
if (!ObjectType(value)) return false;
// [[PromiseState]] 通过算法计算出符合 Promise 状态
if (value not like [[PromiseState]]) {
return false;
}
return true;
}
- Promise 的描述,我们在内部描述有哪些呢?
这里直接参考标准的描述:Table 59 — Internal Slots of Promise Instances,我们这里关注一下 [[PromiseResult]] 的描述:
The value with which the promise has been fulfilled or rejected, if any. Only meaningful if [[PromiseState]] is not "pending".
这里可以思考出的是 Promise 的值只能在 fulfilled or rejected 状态的时候才会被计算,通过的是 <Job Function> 进行延迟计算的,也就是对应我们实现中的 executeResolve, executeReject。
可以继续的思考是:它跟 Rx.js 的实现有哪些差异和相同点呢?
了解上面 3 点应该标准部分也差不多可以描述它自身了,也可以参考 Promise/A+ 等规范来描述其实现的效果,但是个人觉得就用到的时候再了解就行,标准和基础原理知晓后,在知识层面就 ok,剩下的是在具体业务场景的使用经验,用来构成我们的技术理解。
如果有兴趣可以再实现一下其他 Promise 的 API 和静态方法,是一些操作层面的理解。
.defer
.catch
.race
.all
.resolve
.reject
.allSettled
// .etc