Promise 的 resolve 过程和事件循环

Mar 06, 2018 Javascript, node.js

问题来源:https://www.v2ex.com/t/434583

“因为 original 已经被 resolve,所以它的状态被 new Promise 继承” 这句话并不正确,很简单的验证:

1
2
3
4
5
6
7
8
9
10
11
const p1 = new Promise((resolve) => {
resolve(Promise.resolve());
});
console.log(p1); // pending

const p2 = new Promise((resolve) => {
Promise.resolve().then(() => {
resolve();
});
});
console.log(p2); // pending

可以看到在 Promise 的构造函数的参数即 executor 函数,在执行的时候去 resolve 另外一个 Promise,即使这个 Promise 的状态是 “resolved”,也不会在构造函数返回的时候就立即把 promise 对象的状态置为 “resolved”,其实是 “pending”。

但是如果 resolve(original) 和 original.then((value) => resolve(value)) 是等同的话,结果应该是 4 3 2 1 才对)。因为这个 resolve(value) 虽然不是在第一个 event loop 里同步执行的,但是是最早加入 microtask queue 的。说明 V8 在针对 executor 的 resolve 函数的调用时机的处理并不是同步的,其他有些 Promise 的实现(比如 bluebird )是同步的,结果也确实是 4 3 2 1。

V8 的处理好像可以等同于以下代码,看了下这样的话不管是结果还是执行过程中的 Promise 状态都是一致的

1
2
3
4
5
6
7
8
9
10
11
const original = Promise.resolve(2);
new Promise((resolve) => {
process.nextTick(() => {
original.then((value) => {
resolve(value);
})
});
Promise.resolve().then(() => Promise.resolve().then(() => console.log(1)));
console.log(4);
}).then(t => console.log(t));
console.log(3);

总的来说就是,executor 的 resolve 很有可能在 V8 的 Promise 实现里被特殊处理了,resolve(original) 的执行过程都不是在同步代码里,而是加入了 microtask queue。在 microtask queue 里执行的时候又因为是去 resolve 另一个 Promise,相当于 resolve 这个 Promise 的 then 结果,所以又被加入了 microtask queue 的最后面。而最终轮到 resolve(value) 执行的时候,前面已经被一个 Promise.resolve().then() 的回调、以及这个回调带来的另外一个回调给 “插队” 了,所以 resolve(value) 的执行被排在了最后。

没具体去看 V8 的代码,仅仅从表现上分析的。不过这一点确实是规范里也没有提到的东西,跟实现有关。