DEV Community

loading...

Awaiting or just returning asynchronous values in JavaScript async/await functions

Corey Cleary
Tech Lead primarily working with JavaScript and Node.js
Originally published at coreycleary.me ・3 min read

Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up to my newsletter if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets and other freebies.

When you're writing asynchronous code in JavaScript using async/await functions, it can be confusing to know how to handle returning an asynchronous value. Do you need to await the promise before you return? Or should you just return the promise?

Let's look at some examples:

async function getAvailableItems(items) {
  const formattedItems = items.filter(item => !item.name)

  try {
    // checks in database
    return await checkIfItemsAreAvailable(formattedItems)
  } catch(e) {
    console.error(e)
  }
}

How is that different than this?

async function getAvailableItems(items) {
  const formattedItems = items.filter(item => !item.name)

  // checks in database
  return checkIfItemsAreAvailable(formattedItems)
}

So in the first example, we add a try/catch to catch the rejected promise. Imagine the database is down, so when we make the checkIfItemsAreAvailable call, it results in a rejected promise. In the first example, we've caught it!

But in the second example, we haven't. If the checkIfItemsAreAvailable does NOT fail, then it doesn't matter.

Why? Well, when we call getAvailableItems, because it is an async function and async functions return promises anyways, if you await the call to checkIfItemsAreAvailable you just add a bit of extra time waiting for it to resolve before you return it.

Let's look at the ESLint rule for this:

Since the return value of an async function is always wrapped in Promise.resolve, return await doesn’t actually do anything except add extra time before the overarching Promise resolves or rejects. The only valid exception is if return await is used in a try/catch statement to catch errors from another Promise-based function.

So, how to decide?

The question then becomes, are you OK with not catching the rejected promise (i.e. - not using a try/catch) from within the async function?

This can be a tough question mostly because it tends to be application-specific and depends on how your code is currently structured.

Because rejections bubble/propagate, as long as you have a function higher up that catches the rejection, you're probably OK not catching the rejection in the getAvailableItems function.

For example, it's a pattern in Express to catch things at the "root", at the route level, like so:

function wrapAsync(fn) {
  return function(req, res, next) {
    // Make sure to `.catch()` any errors and pass them along to the `next()`
    // middleware in the chain, in this case the error handler.
    fn(req, res, next).catch(next);
  };
}

router.post('/search', wrapAsync(itemController.getAvailableItems))

The route is the "root" level because it is the first bit of code the request will hit (after middleware like authentication, etc). So any rejected promises that happen after that will be caught.

If we just return the promise like so:

async function getAvailableItems(items) {
  const formattedItems = items.filter(item => !item.name)

  // checks in database
  return checkIfItemsAreAvailable(formattedItems)
}

...and it fails, the wrapAsync function at the route level will catch it.

However, you might not have a "root" handler like this in place depending on what kind of application you're working on. So you need to take that into consideration.

Summary

My general rule is if I'm able to try/catch the promise elsewhere, then I'll probably skip the try/catch and just return the promise without awaiting:

async function getAvailableItems(items) {
  const formattedItems = items.filter(item => !item.name)

  // return promise, don't await
  return checkIfItemsAreAvailable(formattedItems)
}

If not, for example, if there are multiple kinds of rejections that can be thrown, I'll use a try/catch.

It takes a bit of practice recognizing the scenarios but the above can serve as a useful guideline to get started.

Love JavaScript but still getting tripped up by asynchronous code? I publish articles on JavaScript and Node every 1-2 weeks, so if you want to receive all new articles directly to your inbox, here's that link again to subscribe to my newsletter!

Discussion (0)