承诺的执行顺序根据返回类型确定。

huangapple go评论63阅读模式
英文:

The execution order of the promise according to the return type

问题

I would like to know the reason why the execution order varies depending on what is returned in a then callback:

如果我想知道执行顺序为什么会根据在then回调中返回的内容而变化:

var a = Promise.resolve();

var b = a.then(function a_then() {
  console.log(1);
  var c = Promise.resolve();
  var d = c.then(function c_then() {
    console.log(2);
  });
  var e = d.then(function d_then() {
    consolesole.log(3);
  });
  console.log(4);

  return c; // <--- this influences the output order

});

var f = b.then(function b_then() {
  console.log(5);
  var g = Promise.resolve();
  var h = g.then(function g_then() {
    console.log(6);
  });
  var i = h.then(function h_then() {
    console.log(7);
  });
  console.log(8);
});

console.log(9);

Without that return c (so returning undefined), the output order is: 9,1,4,2,5,8,3,6,7 and this order was explained as an answer to my previous question.

如果没有return c(返回undefined),输出顺序是:9,1,4,2,5,8,3,6,7,这个顺序是作为对我的上一个问题的回答而解释的。

But adding that return c changes the output order to 9,1,4,2,3,5,8,6,7.

但是加上return c会改变输出顺序为9,1,4,2,3,5,8,6,7。

Why is the value 3 now output before values 5 and 8? Why is the order of the events like this?

为什么值3现在在值5和8之前输出?事件的顺序为什么是这样的?

NB: A similar change in output happens when returning d or e.

注意:当返回de时,输出也会发生类似的变化。

It would be very nice if you could explain it using the table that was used as answer to my previous question:

如果您能使用我之前问题的答案中使用的表格来解释,那将非常好:

Task Action a b c d e f g h i PromiseJob queue
Script a = Promise.resolve() F - - - - - - - -
Script b = a.then(a_then) F ? - - - - - - - a_then
Script f = b.then(b_then) F ? - - - ? - - - a_then
Script console.log(9) F ? - - - ? - - - a_then
Host dequeue a_then F ? - - - ? - - -
a_then console.log(1) F ? - - - ? - - -
a_then c = Promise.resolve() F ? F - - ? - - -
a_then d = c.then(c_then) F ? F ? - ? - - - c_then
a_then e = d.then(d_then) F ? F ? ? ? - - - c_then
a_then console.log(4) F ? F ? ? ? - - - c_then
a_then return resolves b F F F ? ? ? - - - c_then, b_then
Host dequeue c_then F F F ? ? ? - - - b_then
c_then console.log(2) F F F ? ? ? - - - b_then
c_then return resolves d F F F F ? ? - - - b_then, d_then
Host dequeue b_then F F F F ? ? - - - d_then
b_then console.log(5) F F F F ? ? - - - d_then
b_then g = Promise.resolve() F F F F ? ? F - - d_then
b_then h = g.then(g_then) F F F F ? ? F ? - d_then, g_then
b_then i = h.then(h_then) F F
英文:

I would like to know the reason why the execution order varies depending on what is returned in a then callback:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

var a = Promise.resolve();

var b = a.then(function a_then() {
  console.log(1);
  var c = Promise.resolve();
  var d = c.then(function c_then() {
    console.log(2);
  });
  var e = d.then(function d_then() {
    console.log(3);
  });
  console.log(4);

  return c; // &lt;--- this influences the output order

});

var f = b.then(function b_then() {
  console.log(5);
  var g = Promise.resolve();
  var h = g.then(function g_then() {
    console.log(6);
  });
  var i = h.then(function h_then() {
    console.log(7);
  });
  console.log(8);
});

console.log(9); 

<!-- end snippet -->

Without that return c (so returning undefined), the output order is: 9,1,4,2,5,8,3,6,7 and this order was explained as an answer to my previous question.

But adding that return c changes the output order to 9,1,4,2,3,5,8,6,7.

Why is the value 3 now output before values 5 and 8? Why is the order of the events like this?

NB: A similar change in output happens when returning d or e.

It would be very nice if you could explain it using the table that was used as answer to my previous question:

Task Action a b c d e f g h i PromiseJob queue
Script a = Promise.resolve() F - - - - - - - -
Script b = a.then(a_then) F ? - - - - - - - a_then
Script f = b.then(b_then) F ? - - - ? - - - a_then
Script console.log(9) F ? - - - ? - - - a_then
Host dequeue a_then F ? - - - ? - - -
a_then console.log(1) F ? - - - ? - - -
a_then c = Promise.resolve() F ? F - - ? - - -
a_then d = c.then(c_then) F ? F ? - ? - - - c_then
a_then e = d.then(d_then) F ? F ? ? ? - - - c_then
a_then console.log(4) F ? F ? ? ? - - - c_then
a_then return resolves b F F F ? ? ? - - - c_then, b_then
Host dequeue c_then F F F ? ? ? - - - b_then
c_then console.log(2) F F F ? ? ? - - - b_then
c_then return resolves d F F F F ? ? - - - b_then, d_then
Host dequeue b_then F F F F ? ? - - - d_then
b_then console.log(5) F F F F ? ? - - - d_then
b_then g = Promise.resolve() F F F F ? ? F - - d_then
b_then h = g.then(g_then) F F F F ? ? F ? - d_then, g_then
b_then i = h.then(h_then) F F F F ? ? F ? ? d_then, g_then
b_then console.log(8) F F F F ? ? F ? ? d_then, g_then
b_then return resolves f F F F F ? F F ? ? d_then, g_then
Host dequeue d_then F F F F ? F F ? ? g_then
d_then console.log(3) F F F F ? F F ? ? g_then
d_then return resolves e F F F F F F F ? ? g_then
Host dequeue g_then F F F F F F F ? ?
g_then console.log(6) F F F F F F F ? ?
g_then return resolves h F F F F F F F F ? h.then
Host dequeue h_then F F F F F F F F ?
h_then console.log(7) F F F F F F F F ?
h_then return resolves i F F F F F F F F F
Host queue is empty F F F F F F F F F

答案1

得分: 1

a被满足时,将执行回调函数a_then。该回调函数的返回值决定了b的解决方式。如果该then回调返回一个Promise(如c),Promise的核心特性就会生效:Promise b不会简单地用返回的Promise对象来满足,而是会保持处于挂起状态,并且"锁定"在返回的Promise上:Promise c的解决方式将决定Promise b的解决方式——b的命运与c的命运紧密相连。

调用a_then的Promise内部实现通过检查返回的值来实现这一点。它会检测到返回的值是一个thenable(具有then方法),并且实际上计划调用then方法(在这种情况下是c.then),因为这是唯一获取有关该thenable(在这种情况下是c)解决状态的信息的方法。它将自己(内部的)回调传递给该then方法。

这是一个不是你JS代码的一部分的回调函数——它是由Promise实现提供的(参见ECMAScript规范上的Promise resolve functions的步骤13)。此外,c.then的调用将会异步发生(在同一规范中的步骤14中可以看到)。我们可以想象异步任务看起来像这样(简化):

function lockin_b(c) {
    c.then(function fulfill_b(value) {
        _fulfill(value)
    });
}

这里的_fulfill是用于解决b的内部函数。

因此,总结一下,在这个锁定场景中涉及了两个额外的异步任务:

  1. Promise内部通过调用c.then来锁定bc
  2. c被解决时,传递给c.then的回调的异步调用。

这意味着b的解决时间比省略return c时要晚,因此输出5和8的b_then回调也会稍后执行。

以下是在执行该代码期间发生的事件序列。

  • 第一列表示正在执行的内容(主脚本、从事件循环中启动的函数,或从PromiseJob队列中出列的主机)
  • 第二列包含当前正在评估的表达式/语句
  • ai表示具有该名称的Promise的状态:?表示挂起,F表示已满足,R表示已解决但尚未解决(即,已锁定)。
  • 最后一列显示PromiseJob队列中的内容,由主机管理
任务 操作 a b c d e f g h i PromiseJob队列
脚本 a = Promise.resolve() F - - - - - - - -
脚本 b = a.then(a_then) F ? - - - - - - - a_then
脚本 f = b.then(b_then) F ? - - - ? - - - a_then
脚本 console.log(9) F ? - - - ? - - - a_then
主机 出列 a_then F ? - - - ? - - -
a_then console.log(1) F ? - - - ? - - -
a_then c = Promise.resolve() F ? F - - ? - - -
a_then d = c.then(c_then) F ? F ? - ? - - - c_then
a_then e = d.then(d_then) F ? F ? ? ? - - - c_then
a_then console.log(4) F ? F ? ? ? - - - c_then
a_then return c 解决 b F R F ? ? ? - - - c_then, lockin_b
主机 出列 c_then F R F ? ? ? - - - lockin_b
c_then console.log(2) F R F ? ? ? - - - lockin_b
c_then 返回解决 d F R F F ? ? - - - lockin_b, d_then
主机 出列 lockin_b F R F F ? ? -
英文:

When a fulfills, the callback a_then will be executed. The return value of that callback determines how b resolves. If that then callback returns a promise (like c), a core feature of promises kicks in: the promise b will not simply fulfill with that returned promise object, but will instead remain pending and get "locked-in" to the returned promise: the way promise c will settle will determine how b will settle -- b's fate is locked-in to that of c.

The Promise internal implementation that called a_then makes this happen by inspecting the returned value. It detects that this returned value is a thenable (it has a then method), and will actually plan to call that then method (c.then in this case) as that is the only way to get informed about the settled state of that thenable (c in this case). It will pass its own (internal) callback to that then method.

This is a callback that is not part of your JS code -- it is provided by the Promise implementation (see the ECMAScript specification on Promise resolve functions step 13). Moreover, this call of c.then will happen asynchronously (see step 14 in the same specification). We can imagine that asynchronous job to look something like this (simplified):

function lockin_b(c) {
    c.then(function fulfill_b(value) {
        _fulfill(value)
    });
}

...where _fulfill is the internal function with which b can be settled.

So, in summary, there are two extra asynchronous jobs involved in this locked-in scenario:

  1. The asynchronous call of c.then by the Promise internals so to lock-in b to c
  2. The asynchronous call of the callback passed to c.then when c is resolved.

This means that b will settle later than when that return c would have been omitted, and consequently the b_then callback that outputs 5 and 8 will also execute later.

Here is a sequence of events that happen during the execution of that code.

  • The first column represents what is executing (the main script, a function initiated from the event loop, or the host that dequeues an item from the PromiseJob queue)
  • The second column has the current expression/statement being evaluated
  • The columns a to i represent the state of the promise with that name: ? for pending, F for fulfilled and R for resolved, but not yet settled (i.e. locked-in).
  • The last column pictures what is present in the PromiseJob queue, managed by the host
Task Action a b c d e f g h i PromiseJob queue
Script a = Promise.resolve() F - - - - - - - -
Script b = a.then(a_then) F ? - - - - - - - a_then
Script f = b.then(b_then) F ? - - - ? - - - a_then
Script console.log(9) F ? - - - ? - - - a_then
Host dequeue a_then F ? - - - ? - - -
a_then console.log(1) F ? - - - ? - - -
a_then c = Promise.resolve() F ? F - - ? - - -
a_then d = c.then(c_then) F ? F ? - ? - - - c_then
a_then e = d.then(d_then) F ? F ? ? ? - - - c_then
a_then console.log(4) F ? F ? ? ? - - - c_then
a_then return c resolves b F R F ? ? ? - - - c_then, lockin_b
Host dequeue c_then F R F ? ? ? - - - lockin_b
c_then console.log(2) F R F ? ? ? - - - lockin_b
c_then return resolves d F R F F ? ? - - - lockin_b, d_then
Host dequeue lockin_b F R F F ? ? - - - d_then
lockin_b c.then(fulfill_b) F R F F ? ? - - - d_then, fulfill_b
lockin_b return F R F F ? ? - - - d_then, fulfill_b
Host dequeue d_then F R F F ? ? - - - fulfill_b
d_then console.log(3) F R F F ? ? - - - fulfill_b
d_then return resolves e F R F F F ? - - - fulfill_b
Host dequeue fulfull_b F R F F F ? - - -
fulfull_b _fulfill F F F F F ? - - - b_then
fulfull_b return F F F F F ? - - - b_then
Host dequeue b_then F F F F F ? - - -
b_then console.log(5) F F F F F ? - - -
b_then g = Promise.resolve() F F F F F ? F - -
b_then h = g.then(g_then) F F F F F ? F ? - g_then
b_then i = h.then(h_then) F F F F F ? F ? ? g_then
b_then console.log(8) F F F F F ? F ? ? g_then
b_then return resolves f F F F F F F F ? ? g_then
Host dequeue g_then F F F F F F F ? ?
g_then console.log(6) F F F F F F F ? ?
g_then return resolves h F F F F F F F F ? h.then
Host dequeue h_then F F F F F F F F ?
h_then console.log(7) F F F F F F F F ?
h_then return resolves i F F F F F F F F F
Host queue is empty F F F F F F F F F

One can make a similar analysis for when the return statement in the b_then function returns a different promise. It is the same principle.

huangapple
  • 本文由 发表于 2023年6月26日 15:37:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76554499.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定