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.
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.
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.
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.
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.
For microtask resolution, each method call is queued separately. So the order of execution is this:
.then
fires, console.logged..then
fires, rejection, .catch
handler enqueued (NOT called)..catch
resolves, console.logged.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.
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:
builtInFuncs(2)
gets invoked
builtInFuncs(2)
invokes backend(2).then()
backend(2).then()
in effect enqueues console.log(2)
backend(2).then()
returns a promise
(say promise1
)
backend(2).then().catch()
notes the onRejected
handler on promise1
object
builtInFuncs(3)
gets invoked
builtInFuncs(3)
invokes backend(3).then()
backend(3).then()
enqueues a dummy onRejected
handler bacause none is specified
backend(3).then()
returns a promise
(say promise2
)
backend(3).then().catch()
notes the onRejected
handler that calls
console.log(3)
on promise2
asyncAwait(4)
gets invoked
asyncAwait(4)
effectively invokes backend(4).then()
backend(4).then()
enqueues a onFulfilled handler that continues try branch
asyncAwait(5)
gets invoked
asyncAwait(5)
effectively invokes backend(5).then()
backend(5).then()
enqueues a onRejected handler that continues catch branch
handler that prints console.log(2)
gets dequeued
dummy onRejected
handler gets dequeued
promise2
enqueues onRejected
handler it noted which prints console.log(3)
onFulfilled handler that continues try branch gets dequeued
onRejected handler that continues catch branch gets dequeued
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With