本文基于 node 版本 14.13.0,V8 版本 8.4.371。Promise 源码全部位于 V8
1.基本数据结构
Promise 共有 3 种状态,源码如下
-
// Promise constants
-
extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {
-
kPending,
-
kFulfilled,
-
kRejected
-
}
一个新创建的 Promise 处于 pending 状态。当调用 resolve 或 reject 函数后,Promise 处于 fulfilled 或 rejected 状态,此后 Promise 的状态保持不变,也就是说 Promise 的状态改变是不可逆的,Promise 源码中出现了多处状态相关的 assert。
1.2 JSPromise
JSPromise 描述 Promise 的基本信息,源码如下:
-
bitfield struct JSPromiseFlags extends uint31 {
-
status: PromiseState: 2 bit; // Promise 的状态,kPending/kFulfilled/kRejected
-
has_handler: bool: 1 bit; // 是否有处理函数,没有调用过 then 方法的 Promise 没有处理函数
-
handled_hint: bool: 1 bit;
-
async_task_id: int32: 22 bit;
-
}
-
@generateCppClass
-
extern class JSPromise extends JSObject {
-
macro Status(): PromiseState {
-
// 获取 Promise 的状态,返回 kPending/kFulfilled/kRejected 中的一个
-
return this.flags.status;
-
}
-
macro SetStatus(status: constexpr PromiseState): void {
-
// 第 1 个 assert 表示只有 pending 状态的 Promise 才可以被改变状态
-
assert(this.Status() == PromiseState::kPending);
-
// 第 2 个 assert 表示 Promise 创建成功后,不可将 Promise 设置为 pending 状态
-
assert(status != PromiseState::kPending);
-
this.flags.status = status;
-
}
-
macro HasHandler(): bool {
-
// 判断 Promise 是否有处理函数
-
return this.flags.has_handler;
-
}
-
macro SetHasHandler(): void {
-
this.flags.has_handler = true;
-
}
-
// Smi 0 terminated list of PromiseReaction objects in case the JSPromise was
-
// not settled yet, otherwise the result.
-
// promise 处理函数或结果,可以是无/包装了 onFulfilled/onRejected 回调函数的对象/resolve 接收的参数
-
reactions_or_result: Zero|PromiseReaction|JSAny;
-
flags: SmiTagged<JSPromiseFlags>;
大意是说处于 rejected 状态的 Promise 必须要有处理函数。V8 通过 HasHandler 判断 myPromise1 并没有处理函数。当把处理函数加上以后,代码如下:
-
const myPromise1 = new Promise((resolve, reject) => {
-
reject()
-
})
-
myPromise1.then(console.log, console.log)
此时 SetHasHandler 已被调用,HasHandler 返回 true 表示 myPromise1 有处理函数。在 node-v14.13.0 环境下执行,没有错误提示,一切正常
1.3 其它
-
executor:是函数,Promise 构造函数接收的参数
-
PromiseReaction:是对象,表示 Promise 的处理函数,因为一个 Promise 多次调用 then 方法就会有多个处理函数,所以底层数据结构是个链表。
2.构造函数
构造函数源码如下
-
/ https://tc39.es/ecma262/#sec-promise-executor
-
transitioning javascript builtin
-
PromiseConstructor(
-
js-implicit context: NativeContext, receiver: JSAny,
-
newTarget: JSAny)(executor: JSAny): JSAny {
-
// 1. If NewTarget is undefined, throw a TypeError exception.
-
if (newTarget == Undefined) {
-
ThrowTypeError(MessageTemplate::kNotAPromise, newTarget);
-
}
-
// 2. If IsCallable(executor) is false, throw a TypeError exception.
-
if (!Is<Callable>(executor)) {
-
ThrowTypeError(MessageTemplate::kResolverNotAFunction, executor);
-
}
-
let result: JSPromise;
-
// 构造一个 Promise 对象
-
result = NewJSPromise();
-
// 从 Promise 对象 result 身上,获取它的 resolve 和 reject 函数
-
const funcs = CreatePromiseResolvingFunctions(result, True, context);
-
const resolve = funcs.resolve;
-
const reject = funcs.reject;
-
try {
-
// 直接同步调用 executor 函数,resolve 和 reject 做为参数
-
Call(context, UnsafeCast<Callable>(executor), Undefined, resolve, reject);
-
} catch (e) {
-
Call(context, reject, Undefined, e);
-
}
-
return result;
-
}
首先分析两个 ThrowTypeError,以下代码可触发第一个 ThrowTypeError。
-
Promise() // Uncaught TypeError: undefined is not a promise
原因是没有使用 new 操作符调用 Promise 构造函数,此时 newTarget 等于 Undefined,触发了 ThrowTypeError(MessageTemplate::kNotAPromise, newTarget)。
以下代码可触发第二个 ThrowTypeError
-
new Promise() // Uncaught TypeError: Promise resolver undefined is not a function
此时 newTarget 不等于 Undefined,不会触发第一个 ThrowTypeError。但调用 Promise 构造函数时没传参数 executor,触发了第二个 ThrowTypeError。
错误消息在 C++ 代码中定义,使用了宏和枚举巧妙的生成了 C++ 代码,这里不做展开,源码如下
-
T(NotAPromise, "% is not a promise")
-
T(ResolverNotAFunction, "Promise resolver % is not a function")
executor 的类型是函数,在 JavaScript 的世界里,回调函数通常是异步调用,但 executor 是同步调用。在 Call(context, UnsafeCast(executor), Undefined, resolve, reject) 这一行,同步调用了 executor。
-
console.log('同步执行开始')
-
new Promise((resolve, reject) => {
-
resolve()
-
console.log('executor 同步执行')
-
})
-
console.log('同步执行结束')
-
// 本段代码的打印顺序是:
-
// 同步执行开始
-
// executor 同步执行
-
// 同步执行结束
Promise 构造函数接收的参数 executor,是被同步调用的
Promise 构造函数调用 NewJSPromise 获取一个新的 JSPromise 对象。NewJSPromise 调用 PromiseInit 来初始化一个 JSPromise 对象,源码如下
-
macro PromiseInit(promise: JSPromise): void {
-
promise.reactions_or_result = kZero;
-
promise.flags = SmiTag(JSPromiseFlags{
-
status: PromiseState::kPending,
-
has_handler: false,
-
handled_hint: false,
-
async_task_id: 0
-
});
-
promise_internal::ZeroOutEmbedderOffsets(promise);
3.then
3.1 PromisePrototypeThen
JavaScript 层的 then 函数实际上是 V8 中的 PromisePrototypeThen 函数
-
transitioning javascript builtin
-
PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(
-
onFulfilled: JSAny, onRejected: JSAny): JSAny {
-
// 1. Let promise be the this value.
-
// 2. If IsPromise(promise) is false, throw a TypeError exception.
-
const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(
-
MessageTemplate::kIncompatibleMethodReceiver, 'Promise.prototype.then',
-
receiver);
-
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
-
const promiseFun = UnsafeCast<JSFunction>(
-
context[NativeContextSlot::PROMISE_FUNCTION_INDEX]);
-
// 4. Let resultCapability be ? NewPromiseCapability(C).
-
let resultPromiseOrCapability: JSPromise|PromiseCapability;
-
let resultPromise: JSAny;
-
label AllocateAndInit {
-
const resultJSPromise = NewJSPromise(promise);
-
resultPromiseOrCapability = resultJSPromise;
-
resultPromise = resultJSPromise;
-
}
-
// onFulfilled 和 onRejected 是 then 接收的两个参数
-
const onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);
-
const onRejected = CastOrDefault<Callable>(onRejected, Undefined);
-
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
-
// resultCapability).
-
PerformPromiseThenImpl(
-
promise, onFulfilled, onRejected, resultPromiseOrCapability);
-
// 返回一个新的 Promise
-
return resultPromise;
-
}
PromisePrototypeThen 函数创建了一个新的 Promise,获取 then 接收到的两个参数,调用 PerformPromiseThenImpl 完成大部分工作。这里有一点值得注意,then 方法返回的是一个新创建的 Promise。
-
const myPromise2 = new Promise((resolve, reject) => {
-
resolve('foo')
-
})
-
const myPromise3 = myPromise2.then(console.log)
-
// myPromise2 和 myPromise3 是两个不同的对象,有不同的状态和不同的处理函数
-
console.log(myPromise2 === myPromise3) // 打印 false
then 方法返回的是一个新的 Promise