ES6承诺返回值的执行顺序

ANi*_*sus 9 javascript ecmascript-6 es6-promise

为了理解ES6 promise的执行顺序,我注意到链接处理程序的执行顺序受前一个处理程序是返回值还是promise的影响.

let a = Promise.resolve();
a.then(v => Promise.resolve("A")).then(v => console.log(v));
a.then(v => "B").then(v => console.log(v));
Run Code Online (Sandbox Code Playgroud)

直接在Chrome(v 61)控制台中运行时的输出:

B
A.

但是,当点击Run code snippet按钮时,我会收到订单A B.

是否在ES6中为上述示例定义了执行顺序,还是由实现决定?

如果定义了,那么正确的输出应该是什么?

Ry-*_*Ry- 4

Promise.resolve指定返回已解决的承诺(令人兴奋,对吧?25.4.4.5、25.4.1.5、25.4.1.3。)。因此每次都会a.then()立即将作业加入队列(25.4.5.3.1 ,步骤8)。.then()根据此规范,永远不会返回已履行的承诺(对于一些有趣的事情,请Promise.resolve().then()在 Chrome 控制台\xc2\xb9 中尝试)。

\n\n

让\xe2\x80\x99s 将promisea.then(v => Promise.resolve("A"))及其一些相关的规范状态命名为p1 \xc2\xb2。如上所述,这.then()会将要调用的作业排队 ( 25.4.2.1 ) 。a.then(v => Promise.resolve("A"))

\n\n

第一个.then(v => console.log(v))将对应于 \xe2\x82\x81 的承诺反应附加到待处理承诺p1v => console.log(v)的履行反应列表中(仍然是 25.4.5.3.1)。

\n\n
    \n
  • 现在队列是:

    \n\n
      \n
    1. 完成反应工作v => Promise.resolve("A")
    2. \n
  • \n
  • p1现在v => console.log(v)在其完成反应列表中包含 \xe2\x82\x81

  • \n
\n\n

承诺a.then(v => "B")可以是p2。目前它的工作方式相同。

\n\n
    \n
  • 现在队列是:

    \n\n
      \n
    1. 完成反应工作v => Promise.resolve("A")
    2. \n
    3. 完成反应工作v => "B"
    4. \n
  • \n
  • p1 的v => console.log(v)履行反应列表中有\xe2\x82\x81

  • \n
  • p2现在v => console.log(v)在其完成反应列表中有 \xe2\x82\x82
  • \n
\n\n

我们已经到达了脚本的结尾。

\n\n

当对应于 的第一个作业出队v => Promise.resolve("A")并被调用时(再次25.4.2.1),在结果中找到athen(这是重要的部分),导致另一个作业入队(25.4.1.3.2,步骤 12),无论该结果的承诺状态。

\n\n
    \n
  • 现在队列是:

    \n\n
      \n
    1. 完成反应工作v => "B"
    2. \n
    3. Promise.resolve("A").then使用p1 \xe2\x80\x99s [[Resolve]] 和 [[Reject]]进行调用
    4. \n
  • \n
  • p1 的v => console.log(v)履行反应列表中有\xe2\x82\x81

  • \n
  • p2v => console.log(v)在其完成反应列表中有\xe2\x82\x82
  • \n
\n\n

下一个作业将出列并被调用。结果中未找到可调用对象,因此thenp2立即完成再次为25.4.1.3.2,步骤 11a),并为每个p2 \xe2\x80\x99s 完成反应排队一个作业。

\n\n
    \n
  • 现在队列如下:

    \n\n
      \n
    1. Promise.resolve("A").then使用p1 \xe2\x80\x99s [[Resolve]] 和 [[Reject]]进行调用
    2. \n
    3. 调用(通过25.4.2.1v => console.log(v)\xe2\x82\x82
    4. \n
  • \n
  • p1 的v => console.log(v)履行反应列表中有\xe2\x82\x81

  • \n
\n\n

I\xe2\x80\x99m 将在这里停止这一级别的解释,并再次Promise.resolve("A").then开始整个then序列。不过,您可以看到这是怎么回事:作业队列是一个队列,并且将产生输出的一个函数在队列中,而另一个函数尚未添加。队列中的\xe2\x80\x99 将首先运行。

\n\n

正确的输出是 B 后跟 A。

\n\n

那么,既然这样,为什么 Chrome 中的答案在一个页面中是错误的呢?它\xe2\x80\x99s 不是一些堆栈溢出片段填充程序;您可以使用一些 HTML 本身或在 Node.js 中重现它。我的猜测是,它\xe2\x80\x99 是一个破坏规范的优化。

\n\n

\r\n
\r\n
\'use strict\';\r\n\r\nclass Foo extends Promise {}\r\n\r\nlet a = Promise.resolve();\r\na.then(v => Foo.resolve("A")).then(v => console.log(v));\r\na.then(v => "B").then(v => console.log(v));
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

thenable这个有趣的脚本的替代定义node --allow_natives_syntax

\n\n
\'use strict\';\n\nconst thenable = p => ({ then: p.then.bind(p) });\n//const thenable = p => p;\n\nlet a = Promise.resolve();\na.then(v => {\n    %EnqueueMicrotask(() => {\n        %EnqueueMicrotask(() => {\n            console.log("A should not have been logged yet");\n        });\n    });\n\n    return thenable(Promise.resolve("A"));\n}).then(v => console.log(v));\na.then(v => "B").then(v => console.log(v));\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xc2\xb9 对于后代:它\xe2\x80\x99是 Chrome 61.0.3163.100 中已解决的承诺。
\n \xc2\xb2 That\xe2\x80\x99s 不如规范具体,但这是一个试图描述规范而不是规范的答案。运气好的话,它\xe2\x80\x99 也是正确的。

\n