Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my async function being executed before an earlier promise is being fulfilled?

Tags:

javascript

I've written a small program that compares the fulfillment of promises between the .then() approach and the async/await approach. The code runs correctly, however, the output was received in an unexpected order. Can someone please explain why the output is in its current order?

const backend = (num) => {
  const someVar = new Promise((resolve, reject) => {
    if (num % 2 === 0) {
      resolve(`The number, ${num}, is even.`);
    } else {
      reject(`The number, ${num}, is odd.`);
    }
  })
  return someVar;
}
const builtInFuncs = (num) => {
  backend(num)
    .then(message => console.log(message))
    .catch(message => console.log(message));
}
const asyncAwait = async(num) => {
  try {
    const response = await backend(num);
    console.log(response);
  } catch (error) {
    console.log(error);
  }
}

builtInFuncs(2);
builtInFuncs(3);

asyncAwait(4);
asyncAwait(5);

The output I expected is:

The number, 2, is even.
The number, 3, is odd.
The number, 4, is even.
The number, 5, is odd.

The output I receive is:

The number, 2, is even.
The number, 4, is even.
The number, 5, is odd.
The number, 3, is odd.
like image 289
kylejw2 Avatar asked Jul 01 '20 19:07

kylejw2


People also ask

How do you wait for async to finish?

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.

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.

Do async functions automatically return a promise?

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.

Are promises executed asynchronously?

A promise is used to handle the asynchronous result of an operation. JavaScript is designed to not wait for an asynchronous block of code to completely execute before other synchronous parts of the code can run. With Promises, we can defer the execution of a code block until an async request is completed.


2 Answers

For microtask resolution, each method call is queued separately. So the order of execution is this:

  • First call enqueued
  • Second call enqueued
  • Third call enqueued
  • Forth call enqueued
  • First .then fires, console.logged.
  • Second .then fires, rejection, .catch handler enqueued (NOT called).
  • Call in async/await, console.logged
  • Second call to async/await, rejection, catch block enqueued.
  • The .catch resolves, console.logged.
  • The catch block resolves, final log.

Props to Barmar in the comments about the idea of switching the order of the catch and then.

To illustrate a little more simply (clearly?), consider a counter and a Promise function that increments and then decrements it:

let i = 0;
const log = Promise.resolve()
  .then(() => console.log(++i))
  .then(() => console.log(--i));

log();
log();

This will print 1 2 1 0 instead of 1 0 1 0. And if you think about it, this makes a certain amount of sense: the method chain might fail at any step so the runtime enqueues the first call and the second .then only gets enqueued once the first finishes. Otherwise if the first call failed (rejected) it would have to go back and delete the speculatively enqueued second call from the callback queue.

like image 54
Jared Smith Avatar answered Oct 17 '22 06:10

Jared Smith


Before we get to the answer, it is worth mentioning that relying on the order of execution of asynchronous calls is not a good practice. There are two ways to achieve your expected behavior. The preferred method should be:

(async() => {
   await builtInFuncs(2);
   await builtInFuncs(3);
   await asyncAwait(4);
   await asyncAwait(5);
})();

Alternatively, you may rely on the execution order of PromiseReactionJobs guaranteed by ECMAScript Standard. You need to redefine builtInFuncs as:

const builtInFuncs = (num) => {
   backend(num).then(
      message => console.log(message),
      message => console.log(message)
   );
}

Note that both onFulfilled and onRejected handlers are passed to .then() function.

The actual reason for the observed sequence of execution is pretty involved but here is what happens:

  1. builtInFuncs(2) gets invoked

  2. builtInFuncs(2) invokes backend(2).then()

  3. backend(2).then() in effect enqueues console.log(2)

  4. backend(2).then() returns a promise (say promise1)

  5. backend(2).then().catch() notes the onRejected handler on promise1 object

  6. builtInFuncs(3) gets invoked

  7. builtInFuncs(3) invokes backend(3).then()

  8. backend(3).then() enqueues a dummy onRejected handler bacause none is specified

  9. backend(3).then() returns a promise (say promise2)

  10. backend(3).then().catch() notes the onRejected handler that calls console.log(3) on promise2

  11. asyncAwait(4) gets invoked

  12. asyncAwait(4) effectively invokes backend(4).then()

  13. backend(4).then() enqueues a onFulfilled handler that continues try branch

  14. asyncAwait(5) gets invoked

  15. asyncAwait(5) effectively invokes backend(5).then()

  16. backend(5).then() enqueues a onRejected handler that continues catch branch

  17. handler that prints console.log(2) gets dequeued

  18. dummy onRejected handler gets dequeued

  19. promise2 enqueues onRejected handler it noted which prints console.log(3)

  20. onFulfilled handler that continues try branch gets dequeued

  21. onRejected handler that continues catch branch gets dequeued

  22. handler that prints console.log(3) gets dequeued

Note that the promise that backend returns is immediately resolved or rejected. If not, there are more steps involved but effectively the same behavior is observed.

like image 43
virtuoso Avatar answered Oct 17 '22 07:10

virtuoso