查看原文
其他

【第2087期】Promise V8 源码分析(一)

徐鹏跃 前端早读课 2020-10-15

前言

每个人的兴趣多样性,才有这个丰富的世界。今日早读文章由微店@徐鹏跃投稿分享。

正文从这开始~~

本文基于 node 版本 14.13.0,V8 版本 8.4.371。Promise 源码全部位于 V8,node 版本不重要。很早就想写 Promise 源码相关的文章,但 V8 中 Promise 的源码之前(18、19年)是用 CodeStubAssembler 写的,很多前端看不懂 CodeStubAssembler 这种汇编风格的语言,考虑现实选择放弃。直到两个月前无意中发现 V8 的新版本已经用 Torque 重写了 Promise 的大部分代码,遂有此文。预计写 2-3 篇文章,本文介绍的内容是 Promise 构造函数、then 和 resolve。

1.基本数据结构

1.1 三种状态

Promise 共有 3 种状态,源码如下:

  1. // Promise constants

  2. extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {

  3. kPending,

  4. kFulfilled,

  5. kRejected

  6. }

一个新创建的 Promise 处于 pending 状态。当调用 resolve 或 reject 函数后,Promise 处于 fulfilled 或 rejected 状态,此后 Promise 的状态保持不变,也就是说 Promise 的状态改变是不可逆的,Promise 源码中出现了多处状态相关的 assert。

1.2 JSPromise

JSPromise 描述 Promise 的基本信息,源码如下:

  1. bitfield struct JSPromiseFlags extends uint31 {

  2. status: PromiseState: 2 bit; // Promise 的状态,kPending/kFulfilled/kRejected

  3. has_handler: bool: 1 bit; // 是否有处理函数,没有调用过 then 方法的 Promise 没有处理函数

  4. handled_hint: bool: 1 bit;

  5. async_task_id: int32: 22 bit;

  6. }


  7. @generateCppClass

  8. extern class JSPromise extends JSObject {

  9. macro Status(): PromiseState {

  10. // 获取 Promise 的状态,返回 kPending/kFulfilled/kRejected 中的一个

  11. return this.flags.status;

  12. }


  13. macro SetStatus(status: constexpr PromiseState): void {

  14. // 第 1 个 assert 表示只有 pending 状态的 Promise 才可以被改变状态

  15. assert(this.Status() == PromiseState::kPending);

  16. // 第 2 个 assert 表示 Promise 创建成功后,不可将 Promise 设置为 pending 状态

  17. assert(status != PromiseState::kPending);

  18. this.flags.status = status;

  19. }


  20. macro HasHandler(): bool {

  21. // 判断 Promise 是否有处理函数

  22. return this.flags.has_handler;

  23. }


  24. macro SetHasHandler(): void {

  25. this.flags.has_handler = true;

  26. }


  27. // Smi 0 terminated list of PromiseReaction objects in case the JSPromise was

  28. // not settled yet, otherwise the result.

  29. // promise 处理函数或结果,可以是无/包装了 onFulfilled/onRejected 回调函数的对象/resolve 接收的参数

  30. reactions_or_result: Zero|PromiseReaction|JSAny;

  31. flags: SmiTagged<JSPromiseFlags>;

  32. }

当 Promise 状态改变时,比如调用了 resolve/reject 函数,SetStatus 方法会被调用;Javascript 层调用 resolve 方法时,reactionsorresult 字段会被赋值;Javascript 层调用 then 方法时,说明已经有了处理函数,SetHasHandler 会被调用。Status/SetStatus 这两个方法一个获取 Promise 状态,一个设置 Promise 状态,因为容易理解,所以不再举例;下面举一个 HasHandler/SetHasHandler 的例子。

  1. const myPromise1 = new Promise((resolve, reject) => {

  2. reject()

  3. })

在 node-v14.13.0 环境下执行,结果如下:

大意是说处于 rejected 状态的 Promise 必须要有处理函数。V8 通过 HasHandler 判断 myPromise1 并没有处理函数。当把处理函数加上以后,代码如下:

  1. const myPromise1 = new Promise((resolve, reject) => {

  2. reject()

  3. })


  4. myPromise1.then(console.log, console.log)

此时 SetHasHandler 已被调用,HasHandler 返回 true 表示 myPromise1 有处理函数。在 node-v14.13.0 环境下执行,没有错误提示,一切正常。

1.3 其它
  • executor:是函数,Promise 构造函数接收的参数

  • PromiseReaction:是对象,表示 Promise 的处理函数,因为一个 Promise 多次调用 then 方法就会有多个处理函数,所以底层数据结构是个链表。

2.构造函数

构造函数源码如下:

  1. // https://tc39.es/ecma262/#sec-promise-executor

  2. transitioning javascript builtin

  3. PromiseConstructor(

  4. js-implicit context: NativeContext, receiver: JSAny,

  5. newTarget: JSAny)(executor: JSAny): JSAny {

  6. // 1. If NewTarget is undefined, throw a TypeError exception.

  7. if (newTarget == Undefined) {

  8. ThrowTypeError(MessageTemplate::kNotAPromise, newTarget);

  9. }


  10. // 2. If IsCallable(executor) is false, throw a TypeError exception.

  11. if (!Is<Callable>(executor)) {

  12. ThrowTypeError(MessageTemplate::kResolverNotAFunction, executor);

  13. }


  14. let result: JSPromise;

  15. // 构造一个 Promise 对象

  16. result = NewJSPromise();

  17. // 从 Promise 对象 result 身上,获取它的 resolve 和 reject 函数

  18. const funcs = CreatePromiseResolvingFunctions(result, True, context);

  19. const resolve = funcs.resolve;

  20. const reject = funcs.reject;

  21. try {

  22. // 直接同步调用 executor 函数,resolve 和 reject 做为参数

  23. Call(context, UnsafeCast<Callable>(executor), Undefined, resolve, reject);

  24. } catch (e) {

  25. Call(context, reject, Undefined, e);

  26. }

  27. return result;

  28. }

首先分析两个 ThrowTypeError,以下代码可触发第一个 ThrowTypeError。

  1. Promise() // Uncaught TypeError: undefined is not a promise

原因是没有使用 new 操作符调用 Promise 构造函数,此时 newTarget 等于 Undefined,触发了 ThrowTypeError(MessageTemplate::kNotAPromise, newTarget)。

以下代码可触发第二个 ThrowTypeError。

  1. new Promise() // Uncaught TypeError: Promise resolver undefined is not a function

此时 newTarget 不等于 Undefined,不会触发第一个 ThrowTypeError。但调用 Promise 构造函数时没传参数 executor,触发了第二个 ThrowTypeError。

错误消息在 C++ 代码中定义,使用了宏和枚举巧妙的生成了 C++ 代码,这里不做展开,源码如下:

  1. T(NotAPromise, "% is not a promise") \

  2. T(ResolverNotAFunction, "Promise resolver % is not a function") \

executor 的类型是函数,在 JavaScript 的世界里,回调函数通常是异步调用,但 executor 是同步调用。在 Call(context, UnsafeCast(executor), Undefined, resolve, reject) 这一行,同步调用了 executor。

  1. console.log('同步执行开始')

  2. new Promise((resolve, reject) => {

  3. resolve()

  4. console.log('executor 同步执行')

  5. })


  6. console.log('同步执行结束')

  7. // 本段代码的打印顺序是:

  8. // 同步执行开始

  9. // executor 同步执行

  10. // 同步执行结束

Promise 构造函数接收的参数 executor,是被同步调用的

Promise 构造函数调用 NewJSPromise 获取一个新的 JSPromise 对象。NewJSPromise 调用 PromiseInit 来初始化一个 JSPromise 对象,源码如下:

  1. macro PromiseInit(promise: JSPromise): void {

  2. promise.reactions_or_result = kZero;

  3. promise.flags = SmiTag(JSPromiseFlags{

  4. status: PromiseState::kPending,

  5. has_handler: false,

  6. handled_hint: false,

  7. async_task_id: 0

  8. });

  9. promise_internal::ZeroOutEmbedderOffsets(promise);

  10. }

PromiseInit 函数初始化 JSPromise 对象,相关字段可与本文开头介绍的 JSPromise 对象互相印证。

3.then

3.1 PromisePrototypeThen

JavaScript 层的 then 函数实际上是 V8 中的 PromisePrototypeThen 函数,源码如下:

  1. transitioning javascript builtin

  2. PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(

  3. onFulfilled: JSAny, onRejected: JSAny): JSAny {

  4. // 1. Let promise be the this value.

  5. // 2. If IsPromise(promise) is false, throw a TypeError exception.

  6. const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(

  7. MessageTemplate::kIncompatibleMethodReceiver, 'Promise.prototype.then',

  8. receiver);


  9. // 3. Let C be ? SpeciesConstructor(promise, %Promise%).

  10. const promiseFun = UnsafeCast<JSFunction>(

  11. context[NativeContextSlot::PROMISE_FUNCTION_INDEX]);


  12. // 4. Let resultCapability be ? NewPromiseCapability(C).

  13. let resultPromiseOrCapability: JSPromise|PromiseCapability;

  14. let resultPromise: JSAny;

  15. label AllocateAndInit {

  16. const resultJSPromise = NewJSPromise(promise);

  17. resultPromiseOrCapability = resultJSPromise;

  18. resultPromise = resultJSPromise;

  19. }

  20. // onFulfilled 和 onRejected 是 then 接收的两个参数

  21. const onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);

  22. const onRejected = CastOrDefault<Callable>(onRejected, Undefined);


  23. // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,

  24. // resultCapability).

  25. PerformPromiseThenImpl(

  26. promise, onFulfilled, onRejected, resultPromiseOrCapability);

  27. // 返回一个新的 Promise

  28. return resultPromise;

  29. }

PromisePrototypeThen 函数创建了一个新的 Promise,获取 then 接收到的两个参数,调用 PerformPromiseThenImpl 完成大部分工作。这里有一点值得注意,then 方法返回的是一个新创建的 Promise。

  1. const myPromise2 = new Promise((resolve, reject) => {

  2. resolve('foo')

  3. })


  4. const myPromise3 = myPromise2.then(console.log)


  5. // myPromise2 和 myPromise3 是两个不同的对象,有不同的状态和不同的处理函数

  6. console.log(myPromise2 === myPromise3) // 打印 false

then 方法返回的是一个新的 Promise

3.2 PerformPromiseThenImpl

PerformPromiseThenImpl 源码如下:

  1. transitioning macro PerformPromiseThenImpl(implicit context: Context)(

  2. promise: JSPromise, onFulfilled: Callable|Undefined,

  3. onRejected: Callable|Undefined,

  4. resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {

  5. if (promise.Status() == PromiseState::kPending) {

  6. // penging 状态的分支

  7. // The {promise} is still in "Pending" state, so we just record a new

  8. // PromiseReaction holding both the onFulfilled and onRejected callbacks.

  9. // Once the {promise} is resolved we decide on the concrete handler to

  10. // push onto the microtask queue.

  11. const handlerContext = ExtractHandlerContext(onFulfilled, onRejected);

  12. // 拿到 Promise 的 reactions_or_result 字段

  13. const promiseReactions =

  14. UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);

  15. // 考虑一个 Promise 可能会有多个 then 的情况,reaction 是个链表

  16. // 存 Promise 的所有处理函数

  17. const reaction = NewPromiseReaction(

  18. handlerContext, promiseReactions, resultPromiseOrCapability,

  19. onFulfilled, onRejected);

  20. // reactions_or_result 可以存 Promise 的处理函数,也可以存

  21. // Promise 的最终结果,因为现在 Promise 处于 pending 状态,

  22. // 所以存的是处理函数 reaction

  23. promise.reactions_or_result = reaction;

  24. } else {

  25. // fulfilled 和 rejected 状态的分支

  26. const reactionsOrResult = promise.reactions_or_result;

  27. let microtask: PromiseReactionJobTask;

  28. let handlerContext: Context;

  29. if (promise.Status() == PromiseState::kFulfilled) {

  30. handlerContext = ExtractHandlerContext(onFulfilled, onRejected);

  31. microtask = NewPromiseFulfillReactionJobTask(

  32. handlerContext, reactionsOrResult, onFulfilled,

  33. resultPromiseOrCapability);

  34. } else

  35. deferred {

  36. assert(promise.Status() == PromiseState::kRejected);

  37. handlerContext = ExtractHandlerContext(onRejected, onFulfilled);

  38. microtask = NewPromiseRejectReactionJobTask(

  39. handlerContext, reactionsOrResult, onRejected,

  40. resultPromiseOrCapability);

  41. if (!promise.HasHandler()) {

  42. runtime::PromiseRevokeReject(promise);

  43. }

  44. }

  45. // 即使调用 then 方法时 promise 已经处于 fulfilled 或 rejected 状态,

  46. // then 方法的 onFulfilled 或 onRejected 参数也不会立刻执行,而是进入

  47. // microtask 队列后执行

  48. EnqueueMicrotask(handlerContext, microtask);

  49. }

  50. promise.SetHasHandler();

  51. }

3.2.1 PerformPromiseThenImpl 函数的 penging 分支

PerformPromiseThenImpl 有两个分支,pending 分支调用 NewPromiseReaction 函数,在接收到的 onFulfilled 和 onRejected 参数的基础上,生成 PromiseReaction 对象,存储 Promise 的处理函数,并赋值给 JSPromise 的 reactionsorresult 字段;

考虑一个 Promise 可以会连续调用多个 then 的情况,比如:

  1. const myPromise4 = new Promise((resolve, reject) => {

  2. setTimeout(_ => {

  3. resolve('my code delay 5000')

  4. }, 5e3)

  5. })


  6. myPromise4.then(result => {

  7. console.log('第 1 个 then')

  8. })


  9. myPromise4.then(result => {

  10. console.log('第 2 个 then')

  11. })

myPromise4 调用了两次 then 方法,每个 then 方法都会生成一个 PromiseReaction 对象。第一次调用 then 方法时生成对象 PromiseReaction1,此时 myPromise4 的 reactionsorresult 存的是 PromiseReaction1。

第二次调用 then 方法时生成对象 PromiseReaction2,调用 NewPromiseReaction 函数时,PromiseReaction2.next = PromiseReaction1,PromiseReaction1 变成了 PromiseReaction2 的下一个节点,最后 myPromise4 的 reactionsorresult 存的是 PromiseReaction2。PromiseReaction2 后进入 Promise 处理函数的链表,却是链表的头结点。NewPromiseReaction 函数源码如下:

  1. macro NewPromiseReaction(implicit context: Context)(

  2. handlerContext: Context, next: Zero|PromiseReaction,

  3. promiseOrCapability: JSPromise|PromiseCapability|Undefined,

  4. fulfillHandler: Callable|Undefined,

  5. rejectHandler: Callable|Undefined): PromiseReaction {

  6. const nativeContext = LoadNativeContext(handlerContext);

  7. return new PromiseReaction{

  8. map: PromiseReactionMapConstant(),

  9. next: next, // next 字段存的是链表中的下一个节点

  10. reject_handler: rejectHandler,

  11. fulfill_handler: fulfillHandler,

  12. promise_or_capability: promiseOrCapability,

  13. continuation_preserved_embedder_data: nativeContext

  14. [NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]

  15. };

  16. }

在 myPromise4 处于 pending 状态时,myPromise4 的 reactionsorresult 字段示意如下图,下图不是 microtask 队列,下图不是 microtask 队列,下图不是 microtask 队列。

3.2.2 PerformPromiseThenImpl 函数的 fulfilled/rejected 分支

fulfilled/rejected 分支逻辑则简单的多,处理的是当 Promise 处于 fulfilled/rejected 状态时,调用 then 方法的逻辑,以 fulfilled 状态为例,调用 NewPromiseFulfillReactionJobTask 生成 microtask,然后 EnqueueMicrotask(handlerContext, microtask) 将刚才生成的 microtask 放入 microtask 队列。

  1. new Promise((resolve, reject) => {

  2. resolve()

  3. }).then(result => {

  4. console.log('进入 microtask 队列后执行')

  5. })


  6. console.log('同步执行结束')

  7. // 本段代码的打印顺序是:

  8. // 同步执行结束、

  9. // 进入 microtask 队列后执行

尽管调用 then 方法时,Promise 已经处于 resolved 状态,但 then 方法的 onFulfilled 回调函数不会立即执行,而是进入 microtask 队列后执行。

对于一个已经 fulfilled 状态的 Promise,调用其 then 方法时,then 方法接收的 onFulfilled 回调函数不会立即执行。而是进入 microtask 队列后执行,rejected 状态同理 then 方法做的事情简单说就是依赖收集,当 Promise 变成 fulfilled/rejected 状态时,会触发之前收集到的依赖

4.resolve

resolve 函数归根到底调用了 V8 的 FulfillPromise 函数,源码如下:

  1. // https://tc39.es/ecma262/#sec-fulfillpromise

  2. transitioning builtin

  3. FulfillPromise(implicit context: Context)(

  4. promise: JSPromise, value: JSAny): Undefined {

  5. // Assert: The value of promise.[[PromiseState]] is "pending".

  6. // promise 的状态改变是不可逆的

  7. assert(promise.Status() == PromiseState::kPending);


  8. // 取 Promise 的处理函数

  9. const reactions =

  10. UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);


  11. // Promise 已处于 fulfilled 状态,reactions_or_result 存储的不再是

  12. // 处理函数,而是 Promise 的结果

  13. promise.reactions_or_result = value;


  14. // 设置 Promise 的状态为 fulfilled

  15. promise.SetStatus(PromiseState::kFulfilled);


  16. // Promise 的处理函数,Promise 的结果都拿到了,开始正式处理

  17. TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);

  18. return Undefined;

  19. }

FulfillPromise 的逻辑是获取 Promise 的处理函数到 reactions,reactions 的类型是 PromiseReaction,是个链表,忘记的同学可以回看上节的那张链表图片;设置 promise 的 reactionsorresult 为 value,这个 value 就是 JavaScript 层传给 resolve 的参数;调用 promise.SetStatus 设置 promise 的状态为 PromiseState::kFulfilled,最后调用 TriggerPromiseReactions。源码如下:

  1. // https://tc39.es/ecma262/#sec-triggerpromisereactions

  2. transitioning macro TriggerPromiseReactions(implicit context: Context)(

  3. reactions: Zero|PromiseReaction, argument: JSAny,

  4. reactionType: constexpr PromiseReactionType): void {

  5. // We need to reverse the {reactions} here, since we record them on the

  6. // JSPromise in the reverse order.

  7. let current = reactions;

  8. let reversed: Zero|PromiseReaction = kZero;

  9. // 链表反转

  10. while (true) {

  11. typeswitch (current) {

  12. case (Zero): {

  13. break;

  14. }

  15. case (currentReaction: PromiseReaction): {

  16. current = currentReaction.next;

  17. currentReaction.next = reversed;

  18. reversed = currentReaction;

  19. }

  20. }

  21. }

  22. // Morph the {reactions} into PromiseReactionJobTasks and push them

  23. // onto the microtask queue.

  24. current = reversed;

  25. // 链表反转后,调用 MorphAndEnqueuePromiseReaction,把链接中的每一项都进入 microtask 队列

  26. while (true) {

  27. typeswitch (current) {

  28. case (Zero): {

  29. break;

  30. }

  31. case (currentReaction: PromiseReaction): {

  32. current = currentReaction.next;

  33. MorphAndEnqueuePromiseReaction(currentReaction, argument, reactionType);

  34. }

  35. }

  36. }

  37. }

TriggerPromiseReactions 做了两件事:

反转 reactions 链表,前文有分析过 then 方法的实现,then 方法的参数最终存在链表中。最后被调用的 then 方法,它接收的参数被包装后会位于链表的头部,这不符合规范,所以需要反转

遍历 reactions 对象,将每个元素放入 microtask 队列

  1. const myPromise4 = new Promise((resolve, reject) => {

  2. setTimeout(_ => {

  3. resolve('my code delay 5000')

  4. }, 5e3)

  5. })


  6. myPromise4.then(result => {

  7. console.log('第 1 个 then')

  8. })


  9. myPromise4.then(result => {

  10. console.log('第 2 个 then')

  11. })

  12. // 打印顺序:

  13. // 第 1 个 then

  14. // 第 2 个 then

  15. // 如果把 TriggerPromiseReactions 中链表反转的代码注释掉,打印顺序为

  16. // 第 2 个 then

  17. // 第 1 个 then

resolve 的主要工作是遍历上节调用 then 方法时收集到的依赖,放入 microtask 队列中

5.总结与感想

曾经觉得 Promise 很神秘,看了源码觉得 Promise 的本质其实还是回调函数,只不过背靠 Promise 的一系列方法和思想,改变了书写回调函数的方式。then 方法做依赖收集,resolve 将 then 收集到的依赖,放入 microtask 队列中。笔者觉得 Promise 属于微创新,async/await 抛弃回调函数式的写法,暂停/恢复当前代码的执行,是革命性的创新。

关于本文 作者:@徐鹏跃 原文:https://zhuanlan.zhihu.com/p/264944183

为你推荐


【第1956期】JavaScript可视化:Promise和Async/Await


【第2014期】仿照React源码流程打造90行代码的Hooks


欢迎自荐投稿,前端早读课等你来

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存