This article was originally published at https://maximorlov.com/why-you-shouldnt-mix-promise-then-with-async-await/
Have you ever gotten yourself in a tangled mess of code when working with asynchronous JavaScript? A nested callback here, a promise there, and to finish it off, a sparkle of async/await. 😱 🏃🏼♂️💨
I don't know about you, but when I see callbacks, promises and async/await mixed in the same function I'm horrified by the bugs that may lie underneath. 🐛
Working with and, most importantly, reading asynchronous code written in pure async/await is such a breath of fresh air!
I immediately understand what the code does and I can easily make changes if needed.
Aaaaaah. Life is good. 😌
Promise rejections !== async errors
But life wasn't that good this one time when I was working on a Node.js project and started seeing unhandled promise rejections in the production logs.
After some debugging, I eventually traced the issue to an Express middleware function with the following code. Can you tell what's wrong with it?
try {
const rates = await getCurrencyConversions();
Price.update({ value: rates.EUR }, { where: { description: "dollar" } })
.then((result) => {
return res.status(200).json({ message: "Successfully updated conversion rate.", result });
});
} catch (error) {
const message = "Failed to update conversion rate.";
log.error(message, { error });
return res.status(500).json({ message });
}
This code isn't inherently wrong. When everything works without any errors, it does what it's supposed to do.
It's when things don't go as expected that we notice strange behaviour. What do you think happens when the Price.update()
function throws an error?
A: The error is caught in the catch block, logged, and a 500 status code is sent to the client
B: A global unhandledRejection
event is emitted by the Node.js process
.
.
.
.
.
You can probably guess where I'm going with this, the correct answer is B.
Most people would expect that if an error is thrown in the try-block it would be caught and handled in the catch-block. In this case, however, that's far from true because promises have a different error handling mechanism than async/await.
When Price.update()
rejects, it looks for the nearest .catch()
method. If it doesn't find any, Node.js emits a global unhandledRejection
event.
And making matters worse, if you're using Node.js 15 or higher and your application doesn't have an unhandledRejection
event listener, your server will crash!
I could've fixed the issue by appending a .catch()
method to Price.update()
but then I'd end up with two places that are responsible for handling exceptions (.catch() method & catch-block). Code is easier to maintain if errors are handled in one place.
I generally recommend sticking to async/await syntax. There's no need to mix the two syntaxes because if a function returns a promise, you can also use async/await. Async/await is built on top of promises after all.
Note: An exception to this rule is when you cache promises or use Promise.all for more complex asynchronous flows. These advanced promise patterns require you to have a solid understanding of how promises work, at which point you should be able to work your way around unexpected behaviour that arises from mixing async/await with Promise.then() syntax.
When I figured out what the source of the bug was, I refactored the code to async/await and got rid of the .then()
method:
try {
const rates = await getCurrencyConversions();
const result = await Price.update(
{ value: rates.EUR },
{ where: { description: "dollar" } }
);
return res.status(200).json({ message: "Successfully updated conversion rate.", result });
} catch (error) {
const message = "Failed to update conversion rate.";
log.error(message, { error });
return res.status(500).json({ message });
}
After that, the unhandled promise rejections disappeared from the logs and life was good again! 💫
Transform Callbacks into Clean Async Code! 🚀
Tired of messy callback code? Download this FREE 5-step guide to master async/await and simplify your asynchronous code.
In just a few steps, you'll transform complex logic into readable, modern JavaScript that's easy to maintain. With clear visuals, each step breaks down the process so you can follow along effortlessly.
Top comments (0)