了解用于承诺的微任务队列中任务的顺序

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

Understanding order of tasks in the micro queue used for promises

问题

The code you provided is about understanding the Node.js event loop and the behavior of microqueues for promises. Your question is related to why the p2 handler seems to be executed before the p1 handler in a micro queue, contrary to the expected FIFO behavior.

这段代码涉及理解Node.js事件循环以及用于Promise的微任务队列的行为。你的问题与为什么p2处理程序似乎在p1处理程序之前执行在微任务队列中,与预期的FIFO行为相反,有关。

The order of execution you described in your initial understanding is correct:

您在初始理解中描述的执行顺序是正确的:

  1. promise1 resolves instantly, causing its handler (p1) to be added to the micro queue.
  2. promise2 resolves instantly, causing its handler (p2) to be added to the micro queue.
  3. console.log('end test') gets added to the stack, instantly popped, resulting in the log being printed.
  4. Now that the stack is empty, the p1 handler is picked off the queue (FIFO) and added to the stack, resulting in in promise1.then() being printed.
  5. The p2 handler is picked off the queue and added to the stack, resulting in in promise2.then() being printed.

这将导致以下输出:

end test
in promise1.then()
in promise2.then()

However, if you're observing a different behavior where p2 runs before p1, it might be due to specific JavaScript engine optimizations or nuances that can affect the execution order. The microqueue in JavaScript should indeed follow a FIFO order, but there can be optimizations or edge cases that lead to different outcomes.

然而,如果您观察到p2p1之前运行的不同行为,这可能是由于特定的JavaScript引擎优化或细微差别导致的执行顺序。JavaScript中的微任务队列确实应该遵循FIFO顺序,但可能会出现优化或边缘情况,导致不同的结果。

To understand the specific behavior in your case, you may need to consider the JavaScript engine and environment you're using, as different engines may handle this situation differently. Additionally, debugging tools and visualizers like the one you mentioned can help in tracking the execution flow.

为了理解您的情况中的具体行为,您可能需要考虑您正在使用的JavaScript引擎和环境,因为不同的引擎可能会以不同的方式处理此情况。此外,像您提到的调试工具和可视化工具可以帮助跟踪执行流程。

英文:

I am trying to get a better understanding of the Nodejs event loop and have got to the different types of queues that are used under the covers. My question is related to the micro queue used for promises.

From everything I have read, my current understanding is that the micro queue used for promises works on a FIFO basis (first in first out). However I have a small piece of example code that doesn't make sense to me following that principle.

const promise1 = Promise.resolve();
const promise2 = Promise.resolve();

promise2.then(function p2() {
  console.log('in promise2.then()')
})
promise1.then(function p1() {
  console.log('in promise1.then()')
})
console.log('end test')

So from my current (quite possibly flawed understanding) of the order of events that will take place here is

  1. promise1 resolves instantly causing its handler (p1) to be added to the micro queue
  2. promise2 resolves instantly causing its handler (p2) to be added to the micro queue
  3. console.log('end test') gets added to the stack, instantly popped resulting in the log being printed
  4. Now that the stack is empty the p1 handler is picked off the queue (FIFO) and added to the stack resulting in in promise1.then() being printed
  5. The p2 handler is picked off the queue and added to the stack resulting in in promise2.then() being printed

This would result in an output of

end test
in promise1.then()
in promise2.then()

However what I am actually getting is

end test
in promise2.then()
in promise1.then()

I've even tried to run it through a visualiser like this https://www.jsv9000.app/?code=Y29uc3QgcHJvbWlzZTEgPSBQcm9taXNlLnJlc29sdmUoKTsKY29uc3QgcHJvbWlzZTIgPSBQcm9taXNlLnJlc29sdmUoKTsKCnByb21pc2UyLnRoZW4oZnVuY3Rpb24gcDIoKSB7CiAgY29uc29sZS5sb2coJ2luIHByb21pc2UyLnRoZW4oKScpCn0pCnByb21pc2UxLnRoZW4oZnVuY3Rpb24gcDEoKSB7CiAgY29uc29sZS5sb2coJ2luIHByb21pc2UxLnRoZW4oKScpCn0pCmNvbnNvbGUubG9nKCdlbmQgdGVzdCcp

My understanding seems to hold up while the handlers are being added to the micro queue but then everything falls apart once it starts picking things off.

My question is how is the p2 handler being picked off the micro queue and run before the p1 handler if the microqueue is a FIFO system?

答案1

得分: 2

当你示例中的承诺被解决时,还没有处理程序。这些承诺只是立即被履行为undefined,然后分配给它们各自的变量。

只有在这之后,你才调用.then()在这些承诺上,并传递处理程序。因为承诺已经在那时被履行,处理程序会立即被安排执行 - 而由于你在promise1.then(p1)之前进行了promise2.then(p2),所以p2在队列中排在前面,首先被执行。

无论哪种方式,都不应该有关系。这种异步代码在实际情况中并不常见,因为每个承诺都会尽快解决,而独立的承诺链会相互竞争,因此它们的顺序是不可预测的。如果你关心顺序,你可以让它们相互依赖。

英文:

When the promises in your example resolve, there are no handlers yet. The promises are simply immediately fulfilled with undefined, then assigned to their respective variables.

Only after that, you are calling .then() on the promises and passing in the handlers. Since the promises are already fulfilled by then, the handlers immediately get scheduled - and since you are doing promise2.then(p2) before promise1.then(p1), p2 comes first in the queue and is executed first.

Either way, it should not matter. This kind of asynchronous code does not appear in the wild, where every promise is resolved as soon as possible, and independent promise chains race against each other so their order is unpredictable. If you cared about the order, you'd make them depend on each other.

答案2

得分: 2

根据 Promise 的文档

> Promise.resolve() 解决了一个 promise,这与履行或拒绝 promise 不同

如果 promise 立即处于已解决状态,不会将任何内容添加到微任务队列。ECMAScript 语言规范中的27.2.4.7节不指示调用 Promise.resolve() 会创建任何微任务作业。

然后,27.2.5.4.1节说如果在调用 .then 时 promise 已经处于已解决状态,

> 10. 否则,如果 promise.[[PromiseState]] 为已履行状态,那么 c. 执行 HostEnqueuePromiseJob

这基本上意味着每次在已解决的 promise 上调用 .then 都会创建一个微任务(作业)。

我们可以通过查看在相同的已解决 promise 上两次调用 .then 时会发生什么来证明它是 .then 调用创建微任务的,如下所示:

const promise1 = Promise.resolve();
const promise2 = Promise.resolve();

promise2.then(function p2() {
  console.log('in promise2.then()')
})
promise1.then(function p1() {
  console.log('in promise1.then()')
})
promise1.then(function p1() {
  console.log('in promise1.then() a second time')
})

console.log('this text should appear first')

正如输出所示,队列是一个先进先出队列。唯一的混淆点是微任务是由 .then 调用而不是 Promise.resolve() 调用创建的,因为 promises 已经被解决。

如果只在 .then 调用之后才解决 promises,你可以看到它按照最初预期的顺序工作,如下所示:

const refs = {};
const promise1 = new Promise(resolve => refs.resolveP1 = resolve);
const promise2 = new Promise(resolve => refs.resolveP2 = resolve);

promise1.then(function p1() {
  console.log('in promise1.then()')
})
promise2.then(function p2() {
  console.log('in promise2.then()')
})
promise1.then(function p1() {
  console.log('in promise1.then() a second time')
})

refs.resolveP1();
refs.resolveP2();

console.log('this text should appear first')

这次,当调用 .then 时,回调函数将添加到 promise 的 PromiseFulfillReactions 列表中,直到某些事情导致 promise 变为已解决为止:

> 9. 如果 promise.[[PromiseState]] 为挂起状态,则 a. 将 fulfillReaction 追加到 promise.[[PromiseFulfillReactions]] 中。

请注意,在前面示例的输出中,两个 P1 消息首先出现,尽管 .then 调用的顺序并非如此。这是因为它们在 P1 的 PromiseFulfillReactions 列表中,并且都在 P1 解决时执行。

英文:

According to the Promise documentation:

> Promise.resolve() resolves a promise, which is not the same as
> fulfilling or rejecting the promise

If the promise is immediately in a resolved state, nothing is added to the microtask queue. The ECMAScript Language Specification in section 27.2.4.7 does not indicate that any microtask job is created as a result of calling Promise.resolve()

Then, section 27.2.5.4.1 says that if the promise is already in a resolved state when .then is called,

> 10. Else if promise.[[PromiseState]] is fulfilled, then c. Perform HostEnqueuePromiseJob

This is essentially saying that a microtask (job) is created every single time .then is called on a resolved promise.

We can demonstrate that it is the .then call which creates a microtask, by seeing what happens when we call .then twice on the same resolved promise:

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

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

const promise1 = Promise.resolve();
const promise2 = Promise.resolve();

promise2.then(function p2() {
  console.log(&#39;in promise2.then()&#39;)
})
promise1.then(function p1() {
  console.log(&#39;in promise1.then()&#39;)
})
promise1.then(function p1() {
  console.log(&#39;in promise1.then() a second time&#39;)
})

console.log(&#39;this text should appear first&#39;)

<!-- end snippet -->

As the output indicates, the queue is a FIFO queue. The only confusion was that the microtasks are created by .then calls rather then by the Promise.resolve() calls, because the promises are already resolved.

You can see it work in the originally expected order if the promises are only resolved after the .then calls:

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

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

const refs = {};
const promise1 = new Promise(resolve =&gt; refs.resolveP1 = resolve);
const promise2 = new Promise(resolve =&gt; refs.resolveP2 = resolve);

promise1.then(function p1() {
  console.log(&#39;in promise1.then()&#39;)
})
promise2.then(function p2() {
  console.log(&#39;in promise2.then()&#39;)
})
promise1.then(function p1() {
  console.log(&#39;in promise1.then() a second time&#39;)
})

refs.resolveP1();
refs.resolveP2();

console.log(&#39;this text should appear first&#39;)

<!-- end snippet -->

This time, when .then is called, the callback functions are added to the PromiseFulfillReactions list for the promise, and are not invoked until something causes the promise to become resolved:

> 9. If promise.[[PromiseState]] is pending, then a. Append fulfillReaction to promise.[[PromiseFulfillReactions]].

Note that in the output of the preceding example, the two P1 messages appear first, even though the .then calls are not in that order. This is because they are in the PromiseFulfillReactions list for P1, and all execute at the time that P1 is resolved.

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

发表评论

匿名网友

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

确定