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:
- Execute the handler, the handler is the callback registered in then(), and get the return result.
- 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 itsthen
method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of thethen
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。