Skip to content

由一道题引发的Promise实现思考 #15

Open
@jappp

Description

@jappp

先来看题吧,以下代码执行后的输出是什么呢?

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);
}).then(() =>{
  console.log(6);
})

浏览器中输出为「0 1 2 3 4 5 6」, 你可能会感觉有点奇怪,4为什么会跑到3之后才输出呢?

如果说需要等待返回的 Promise.resolve(4) 状态变更, 那也只要等待一个 tick,也就是创建一个微任务去处理就行了,最终输出「0 1 2 4 3 5 6」。这正是使用我们之前根据 Promise/A+ 规范手写的 MyPromise 会输出的结果。
之前的代码如下:

// 如果返回的是一个 Promise 对象
if (typeof then === "function") {
    // 让then执行,第一个参数是this,后面是成功的回调 和 失败的回调
    then.call(
      x,
      (res) => {
        // 成功和失败只能调用一个
        if (called) return;
        called = true;
        // 递归解析的过程(因为可能 promise 中还有 promise)
        resolvePromise(promise2, res, resolve, reject);
      },
      (err) => {
        // 成功和失败只能调用一个
        if (called) return;
        called = true;
        reject(err); // 失败了就失败了
      }
    );
}

但实际在浏览器中的表现是创建了两个微任务导致 4 会延迟两个 tick 输出。所以问题来了:为什么之前编写的通过所有 Promise/A+ 测试用例的代码,执行结果会和原生 Promise 不一样?

这个就得从 ECMA 规范中解释了:

  • 在 ECMA 规范中,promise.then()中的 onFullFilled 和 onRejected 两个回调函数,会在 promise 状态不为 pending 时被加入到微任务队列,该微任务在规范中被称为 PromiseReactionJob。
  • 如果 A.then() 的回调函数返回值是一个 Promise 对象(命名为B),那么会生成一个微任务。这个微任务的内容是调用A.then(resolvePromise1, rejectPromise1),将 promiseA 和 promiseB 关联起来。简单来说就是 A 需要等待 B 的状态转换,所以多用了一个 tick 将 A 的状态 bind 到 B上。这就是原生 Promise 多创建的一个微任务。

Promise/A+ 规范似乎并没有对这个规范做要求,依据 A+ 规范实际使用中好像也没什么副作用影响,感觉两次微任务没什么必要性,目前的 Promise 日常操作,一次微任务都是可以满足的。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions