Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this async function execute before the equivalent Promise.then chain defined before it?

I have the following code:

var incr = num => new Promise(resolve => {
  resolve(num + 1);
});

var x = incr(3)
  .then(resp => incr(resp))
  .then(resp => console.log(resp));

async function incrTwice(num) {
  const first = await incr(num);
  const twice = await incr(first);
  console.log(twice);
}

incrTwice(6);

Which I believe (perhaps mistakenly) shows two equivalent ways to achieve the same functionality: first by chaining promises and second with the syntactic sugar of async/await.

I would expect the promise chaining solution to console.log first, then the async function second, however the async function console.log's first then the promise chaining solution prints.

My logic is as follows:

  1. xs initial resolve would be first on the microtask queue as the declaration is processed
  2. the stack is empty between the declaration of x and incrTwice which would cause the microtask queue to be flushed (resulting in the completion of the promise chain)
    • x prints first
  3. incrTwice is defined
  4. incrTwice executes queueing on the microtask queue at the awaits eventually printing to the console
    • incrTwice prints second

Clearly I have a misunderstanding somewhere, is someone able to point out where I am wrong?

like image 749
Grant Avatar asked Jun 16 '20 09:06

Grant


People also ask

Why async await over simple promise chains?

Promise chains can become difficult to understand sometimes. Using Async/Await makes it easier to read and understand the flow of the program as compared to promise chains.

Why do async functions return a promise?

Async functions can contain zero or more await expressions. Await expressions make promise-returning functions behave as though they're synchronous by suspending execution until the returned promise is fulfilled or rejected. The resolved value of the promise is treated as the return value of the await expression.

Can you pass an async function to a promise?

Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.

When using an async function what is always returned?

The word “async” before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically. So, async ensures that the function returns a promise, and wraps non-promises in it.


2 Answers

First of all, let me point out that you never should argue about the execution order of independent promise chains. There are two asynchronous calls, and they do not depend on each other but run concurrently, so they should always be expected to finish in arbitrary order.

Toy examples that only use immediately-resolving promises make this order depend on microtask queueing semantics instead of actual asynchronous tasks, which makes this a purely academic exercise (whose result is subject to changes in the spec).

Anyway, let's clear up your misunderstandings:

the stack is empty between the declaration of x and incrTwice which would cause the microtask queue to be flushed

No, the stack only becomes empty after all user code is ran to completion. There's still the global execution context of the <script> element on the stack. No microtasks are executed until all synchronous code (incr = …, x = incr(3).… and incrTwice(6)) has finished.

I believe [the code] shows two equivalent ways to achieve the same functionality: first by chaining promises and second with the syntactic sugar of async/await.

Not exactly. The .then() chaining has an additional resolve step when unnesting the incr(resp) promise that is returned from the first .then(…) handler. To make it behave precisely the same as the awaited promises in incrTwice, you'd need to write

incr(3).then(resp =>
  incr(resp).then(resp =>
    console.log(resp)
  )
);

If you do that, you'll actually get the console logs in the order in which you started the two promise chains because they will take the same number of microtasks until the console.log() is executed.

For more details, see What is the order of execution in javascript promises, Resolve order of Promises within Promises, What happen when we return a value and when we return a Promise.resolve from a then() chain, in the microtask queue?, What is the difference between returned Promise?, ES6 promise execution order for returned values

like image 166
Bergi Avatar answered Oct 17 '22 16:10

Bergi


Your thinking is understandable and logical. The reason for the observed behaviour has to do with one of the guarantees that were built into the Promise API, namely that promises are always asynchronous in execution, even if they carry out synchronous operations (like resolving the promise immediately). More technically, this means a promise's callback will never be called until the current run has completed.

As MDN puts it:

Callbacks will never be called before the completion of the current run of the JavaScript event loop.

So:

Promise.resolve(10).then(value => console.log(value));
console.log(20); //20, 10 - NOT 10, 20

I cover this in my guide to promises which can be found here.

like image 32
Mitya Avatar answered Oct 17 '22 15:10

Mitya