英文:
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
.
注意:当返回d
或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 |
英文:
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; // <--- 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
的内部函数。
因此,总结一下,在这个锁定场景中涉及了两个额外的异步任务:
- Promise内部通过调用
c.then
来锁定b
到c
- 当
c
被解决时,传递给c.then
的回调的异步调用。
这意味着b
的解决时间比省略return c
时要晚,因此输出5和8的b_then
回调也会稍后执行。
以下是在执行该代码期间发生的事件序列。
- 第一列表示正在执行的内容(主脚本、从事件循环中启动的函数,或从PromiseJob队列中出列的主机)
- 第二列包含当前正在评估的表达式/语句
- 列
a
到i
表示具有该名称的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:
- The asynchronous call of
c.then
by the Promise internals so to lock-inb
toc
- The asynchronous call of the callback passed to
c.then
whenc
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
toi
represent the state of the promise with that name:?
for pending,F
for fulfilled andR
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论