4
头图

Knowledge blind spot caused by an example question

 // promise1
new Promise(resolve => {
  resolve(
    new Promise(resolve => {
      resolve(1);
    })
  );
}).then(res => {
  console.log('1');
});

new Promise(resolve => {
  resolve(2);
})
  .then(() => {
    console.log('2');
  })
  .then(() => {
    console.log('3');
  })
  .then(() => {
    console.log('4');
  });

// 输出顺序:
// 2
// 3
// 1
// 4

Let's look at an example question first. According to my previous understanding, I thought the output order was 2 1 3 4 . Then through debugging, I found that the state of promise1 is still pending after initialization, and I feel that I still have insufficient understanding of Promise microtasks. After studying the ECMA specification, I finally figured out this problem.

Types of Microtasks

The ECMA specification divides Promise microtasks into two types. Let's look at the timing and execution content of these two microtasks in combination with the specification.

NewPromiseReactionJob

This microtask occurs when the callback registered in then() is executed after the Promise is resolved, or when the Promise is resolved when then() is registered. To simplify the description, let's focus on this scenario: the current Promise has been resolved, and then then() is called, for example:

 Promise.resolve(1).then(res => console.log(res));

res => console.log(res) is running in this microtask. Take a look at what the spec says about Promise.prototype.then:

Because the Promise is already in the fulfilled state, let's look at the operation of the fulfilled state in PerformPromiseThen:

Here, a NewPromiseReactionJob microtask is created and added to the microtask queue. Let's take a look at how NewPromiseReactionJob is executed:

This microtask mainly includes two contents:

  1. Execute the handler, the handler is the callback registered in then(), and get the return result.
  2. Perform resolve (return result) or reject (return result) on the new Promise produced in then().

NewPromiseResolveThenableJob

The above micro-tasks are basically familiar to everyone, and this type of micro-tasks is the blind spot mentioned in the example question. First notice the description of the resolve function:

If an object's then property can be called (is a function), then the object is a thenable object. Call resolve() if the passed parameter value is a thenable object, a microtask such as NewPromiseResolveThenableJob will be generated. Let's take a look at the content of this microtask:

It probably means that this kind of microtask generates the following code:

 // resovle 和 reject 是调用 resolve(thenable) 时那个 Promise 上的。
thenable.then(resolve, reject);

Then combined with the first microtask, if the object of thenable is a Promise, the first microtask will be generated after the microtask is executed. Why do you want to do this? There is an explanation in the specification:

This Job uses the supplied thenable and its then method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of the then method occurs after evaluation of any surrounding code has completed.

Direct translation probably means that this will not be executed until the surrounding synchronous code is executed. Regarding this design intent, my understanding is to consider the thenable object is not necessarily a Promise instance, it may be any object created by the user; if this object's then is a synchronization method, Then doing this can ensure that the execution order of then is also in the microtask.

Analysis of sample questions

Let's analyze the example problem again:

 // promise1
new Promise(resolve => {
  resolve(
    // promise2
    new Promise(resolve => {
      resolve(1);
    })
  );
}).then(res => {
  console.log('1');
});

// promise3
new Promise(resolve => {
  resolve(2);
})
  .then(() => {
    console.log('2');
  })
  .then(() => {
    console.log('3');
  })
  .then(() => {
    console.log('4');
  });

After the code is executed, we use pseudo code to represent the content of the microtask queue:

 const microTasks = [
  function job1() {
    promise2.then(promise1.[[Resolve]], promise1.[[Reject]]);
  },
  function job2() {
    const handler = () => {
      console.log('2');
    };
    
    // 决议 then() 返回的新 Promise。
    resolve(handler(2));
  }
];

Then start executing the microtask queue. After job 1 is executed, a new microtask job 3 is generated:

 const microTasks = [
  function job2() {
    const handler = () => {
      console.log('2');
    };
    resolve(handler(2));
  },
  function job3() {
    const handler = promise1.[[Resolve]];
    resolve(handler(1));
  }
];

After job 2 is executed, 2 is output, and a new microtask job 4 is generated:

 const microTasks = [
  function job3() {
    const handler = promise1.[[Resolve]];
    resolve(handler(1));
  },
  function job4() {
    const handler = () => {
      console.log('3');
    };
    resolve(handler(undefined));
  }
];

Note that the content of job 3 is to make promise1 resolve, then the then callback of promise1 will be executed, and another microtask job 5 will be generated; and after job 4 is executed, the output becomes 2 3 , and let The new Promise resolution generated by then() will also generate the next microtask job 6:

 const microTasks = [
  // job 5 由 job 3 产生。
  function job5() {
    const handler = () => {
      console.log('1');
    };
    resolve(handler(1));
  },
  function job6() {
    const handler = () => {
      console.log('4');
    };
    resolve(handler(undefined));
  }
];

Then the final output is 2 3 1 4 , you can put the above analysis method in other questions to verify whether Kangkang is right.

My JS Blog: Whisper JavaScript

References


deepfunc
776 声望634 粉丝