这其实是一个非常有趣的问题,因为承诺/A+ 规格 https://promisesaplus.com/将允许第一个代码版本产生与第二个代码版本相同的输出。
有人可能会驳回这个问题,说 Promise 的实现没有说明如何实现resolve(p)
将被实施。在查看 Promise/A+ 规范时,这是一个真实的陈述,引用其前言:
核心 Promises/A+ 规范不涉及如何创建、履行或拒绝 Promise,...
但是 Promise 的 EcmaScript 规范(第25.4 https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects)比 Promise/A+ 规范更加详细,并要求将“作业”添加到相关作业队列的后面——对于 Promise 结算来说,这是乔布斯 queue (25.4.1.3.2 https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects and 8.4 https://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues):这决定了特定的顺序:
所需的作业队列
[...]
乔布斯:对 Promise 解决的响应的作业
[...]
The 待处理的作业来自单个作业队列的记录始终按 FIFO 顺序启动
它还定义了resolve(p)
- 什么时候p
是一个thenable——意志first将一个作业放入队列中,该作业将执行必要的内部调用p.then
方法。这是not立即完成。引用 EcmaScript 规范中的注释:25.4.2.2 https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob:
此过程必须作为一项工作进行,以确保对then
方法在任何周围代码的评估完成后发生。
以下代码片段中的输出顺序说明了该语句:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called asynchronously when triggered by resolve(p1)");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
resolve(p1);
console.log("Code that follows is executed synchronously, before p1.then is");
});
当我们使用p1.then(resolve)
方法调用而不是resolve(p1)
,我们得到相反的顺序:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called synchronously now");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
p1.then(resolve);
console.log("Code that follows is executed synchronously, after p1.then is");
});
你的代码
上面确实解释了您得到的不同输出顺序。以下是第一个代码版本如何对操作进行排序。首先让我稍微重写一下,以便大多数涉及的承诺都有一个名称:
const p1 = Promise.resolve();
const p2 = new Promise((resolve) => resolve(p1));
const p3 = p2.then(() => console.log('after:await'));
const p4 = p1.then(() => console.log('tick:a'));
const p5 = p4.then(() => console.log('tick:b'))
const p6 = p5.then(() => console.log('tick:c'));
现在,在主同步代码执行完成之后,仅p1
具有已解决状态,并且作业队列(微任务队列)上存在两个作业,其中一个是由于resolve(p1)
第二个是因为p1.then
:
-
根据25.4.2.2 https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob,
the then
的方法p1
称为传递内部[[resolve]]
相关函数p2
. The p1.then
内部人士知道p1
已解决并将另一个作业放入队列中以实际解决p2
!
-
带有“tick:a”的回调被执行,promise p4 被标记为已完成,在作业队列中添加一个新作业。
现在队列中有 2 个新作业,按顺序处理:
-
执行步骤 1 中的作业:p2 现在已解析。这意味着一个新作业排队等待实际调用相应的then
回调
-
执行步骤 2 中的作业:执行带有“tick:b”的回调
只有稍后,步骤 3 中添加的作业才会被执行,这将通过“after:await”调用回调。
所以,总而言之。在 EcmaScript 中resolve(p)
, where p
是一个 thenable 涉及一个异步作业,它本身会触发另一个异步作业来通知完成。
The then
区分第二个代码版本的回调,只需要one异步作业被调用,因此它发生在“tick:b”输出之前。