DEV Community

Cover image for Why You Shouldn't Mix Promise.then() With Async/Await Syntax
Maxim Orlov
Maxim Orlov

Posted on • Updated on • Originally published at maximorlov.com

Why You Shouldn't Mix Promise.then() With Async/Await Syntax

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 });
}
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

After that, the unhandled promise rejections disappeared from the logs and life was good again! 💫

Master Asynchronous JavaScript 🚀

Learn how to write modern and easy-to-read asynchronous code with a FREE 5-day email course.

Through visual graphics you will learn how to decompose async code into individual parts and put them back together using a modern async/await approach. Moreover, with 30+ real-world exercises you'll transform knowledge into a practical skill that will make you a better developer.

Refactoring Callbacks, a FREE 5-day email course. 30+ real-world exercises, a visual guide and 5 days, 5 lessons.

👉 Get Lesson 1 now

Top comments (0)