PromiseResolveThenableJobTask 微任务是怎么被执行的

背景

要理解 NewPromiseResolveThenableJobTask 这个微任务的作用,需要先回到它的创建场景 :当一个 Promise 的 then 回调(或其他 Promise 相关回调)返回了一个 可 thenable 对象 (通常是另一个 Promise,但也包括自定义的、有 then 方法的对象)时,JS 引擎会创建这个微任务,本质是为了 确保 "返回的 thenable 对象" 的状态完全确定后,再继续执行后续的 Promise 链

如下代码,后续讨论依托于此段代码展开:

js 复制代码
Promise.resolve() 
.then(() => { 
    console.log(0); 
    return Promise.resolve(4); 
}) 
.then((res) => { 
    console.log(res); 
});

先明确前提:为什么需要这个微任务?

Promise 链的核心规则是 "后续的 then 回调,必须等待前一个 then 回调的 "返回值" 状态确定后才执行 "。如果前一个 then 回调返回的是一个普通值(比如 4、'a'),那很简单:直接把这个普通值作为下一个 Promise 的 "成功值",立刻触发下一个 then 的微任务。

但如果返回的是一个Promise(或可 thenable 对象) (比如代码中 return Promise.resolve(4)),情况就复杂了:这个返回的 Promise 本身是 "异步状态"(即使是 Promise.resolve(4) 这种 "立即成功" 的,也不是同步执行),必须等待它的 then 回调执行完(状态完全确定),才能把它的结果传给下一个 then

这时候,NewPromiseResolveThenableJobTask 就是用来干这个 "等待并传递结果" 的活的。

NewPromiseResolveThenableJobTask 微任务具体做什么?

它的执行逻辑可以拆解为 3 个核心步骤,本质是 "代理执行返回的 thenable 对象的 then 方法,并把结果同步给当前 Promise 链":

1. 先明确这个微任务的 "3 个输入参数"

在创建这个微任务时,引擎会传入 3 个关键信息(对应 ECMA 规范的定义: #sec-newpromiserelovethenablejobtask):

  • promiseToResolve:当前 Promise 链中的 "中间 Promise"(即前一个 then 执行后返回的新 Promise,后续的 then 都是挂在这个 Promise 上的)。
  • thenable:前一个 then 回调返回的 "可 thenable 对象"(比如代码中 return Promise.resolve(4) 里的这个 Promise)。
  • thenthenable 对象自身的 then 方法(比如 Promise 原型上的 Promise.prototype.then)。
js 复制代码
    const promiseToResolve = Promise.resolve().then(() => { 
        return [[thenable 对象]]: { 
            [[then 方法]]: (onFulfilled, onRejected) => {}
        }
     })

2. 微任务执行的核心逻辑

当这个微任务被执行时,会干两件关键的事:

第一步:调用 thenablethen 方法,绑定 "结果传递回调"

微任务会主动调用 thenable.then(...),并传入两个回调函数(相当于 "代理回调"):

  • 成功回调(onFulfilled):如果 thenable 成功(比如 Promise.resolve(4) 成功),就会触发这个回调,把 thenable 的成功值(比如 4)传给 promiseToResolve,让 promiseToResolve 也变成 "成功状态",并把 4 作为它的成功值。
  • 失败回调(onRejected):如果 thenable 失败(比如 Promise.reject(5)),就会触发这个回调,把 thenable 的失败原因传给 promiseToResolve,让 promiseToResolve 变成 "失败状态"。
第二步:触发后续的 Promise 链

一旦 promiseToResolve 的状态被确定(成功 / 失败),引擎就会为 promiseToResolve 上挂载的后续 then 回调(比如代码中第二个 then((res) => console.log(res)))创建新的微任务,把刚才传递过来的结果(比如 4)传给这个后续回调,让 Promise 链继续执行。

结合代码,看这个微任务的具体作用

javascript 复制代码
Promise.resolve() // 1. 初始 Promise(成功状态,值为 undefined)
  .then(() => { // 2. 第一个 then 回调(cb1)
    console.log(0); // 3. 同步打印 0
    return Promise.resolve(4); // 4. 返回一个新的 Promise(记为 P4)
  })
  .then((res) => { // 5. 第二个 then 回调(cb2,挂在 "中间 Promise":P_mid 上)
    console.log(res); // 6. 最终打印 4
  });

这里 NewPromiseResolveThenableJobTask 的执行过程完全贴合上面的逻辑:

  1. 执行 cb1 时,返回了 Promise.resolve(4)(即 thenable = P4),引擎创建 NewPromiseResolveThenableJobTask 微任务,传入:

    • promiseToResolve = P_mid(第一个 then 返回的中间 Promise,cb2 挂在它上面)
    • thenable = P4
    • then = P4.then(即 Promise 原型的 then 方法)。
  2. 当这个微任务执行时:

    • 调用 P4.then(成功回调, 失败回调):因为 P4Promise.resolve(4)(已成功),触发 PromiseResolveThenableJobTask的执行流程(重点)。
    • "成功回调" 把 4 传给 P_mid,让 P_mid 变成 "成功状态",值为 4。
    • 引擎为 P_mid 上的 cb2 创建新微任务,后续执行这个微任务时,就会把 4 传给 cb2,打印出 4

重点核心(PromiseResolveThenableJobTask被执行):

PromiseResolveThenableJobTask 的被执行过程,esma 规范 的描述

其实它规定了这个 PromiseResolveThenableJobTask 要作为异步任务去执行(加入任务队列);

v8::builtins::promise-resolve.tq 的处理是一样的。v8 把 它加入了 微任务队列

但是,这个任务后续的被执行,就没有说了,规范上我并没有找到;

但是,看了 PromiseResolveThenableJobTaskv8 具体实现, 它分了两种情况来处理的:

  1. thenable对象Promise对象时;执行 PerformPromiseThen的实现方式;
    • 如果 promise 是 pendding 态,会创建一个 NewPromiseReaction 任务, 等待 promise 兑现;
    • 如果 promise 是 fulfiled|rejected, 则会创建一个微任务,加入到微任务队列,等待执行;
  2. thenable对象非Promise 对象时;直接走 resolve function 逻辑;
    • 直接调用 resolve(结果),兑现 promise;

如图(PromiseResolveThenableJob):

如图(PerformPromiseThenImpl):

也就是说,以下两段代码输出的结果是不一样的:

js 复制代码
Promise.resolve()
  .then(() => {
    console.log(0);
    return Promise.resolve(4);
  })
  .then((res) => {
    console.log(res);
  });

Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(5);
  });
  
// 输出:0,1,2,3,4,5 
js 复制代码
Promise.resolve()
  .then(() => {
    console.log(0);
    return {
      then(onFulfilled, onRejected) {
        onFulfilled(4);
      },
    };
  })
  .then((res) => {
    console.log(res);
  });

Promise.resolve()
  .then(() => {
    console.log(1);
  })
  .then(() => {
    console.log(2);
  })
  .then(() => {
    console.log(3);
  })
  .then(() => {
    console.log(5);
  });

// 输出:0,1,2,4,3,5

一句话总结

NewPromiseResolveThenableJobTask 就是一个 "结果传递代理微任务":专门处理 "then 回调返回 thenable 对象" 的场景,确保这个 thenable 对象的状态确定后,把它的结果同步给当前 Promise 链的中间 Promise,再触发后续的 then 回调,保证 Promise 链的顺序和状态正确性。

注意的点是:

thenable对象是 Promise对象 和 普通包含实现 then 方法的对象,PromiseResolveThenableJobTask 的处理方式是不一样的;

相关推荐
华仔啊36 分钟前
CSS常用函数:从calc到clamp,实现动态渐变、滤镜与变换
前端·css
大杯咖啡37 分钟前
基于 Vue3 (tsx语法)的动态表单深度实践-只看这一篇就够了
前端·javascript·vue.js
Aniugel37 分钟前
Vue2简单实现一个权限管理
前端·vue.js
乐无止境38 分钟前
系统性整理组件传参14种方式
前端
爱泡脚的鸡腿39 分钟前
uni-app D8 实战(小兔鲜)
前端·vue.js
睡神雾雨41 分钟前
Vite 环境变量配置经验总结
前端
咪库咪库咪41 分钟前
vue5
前端
前端缘梦41 分钟前
JavaScript核心机制:执行栈、作用域与this指向完全解析
前端·javascript·面试