DEV Community

gitfudge
gitfudge

Posted on • Edited on

27 15

JavaScript: Error handling with Promises and Async/Await

Error handling in JavaScript can be easy while being tricky at certain places, especially Promises. JS allows error handling with the help of try, catch, and throw.

const main = () => {
  try {
    // Do something super crazy
    if (!bakePizza()) {
      throw new Error('Oh no!');
    }
  } catch (error) {
    // That escalated quickly
    handleOvenExplosion();
  }
}
Enter fullscreen mode Exit fullscreen mode

This seems simple enough but gets a little tricky when Promises are involved.

Let’s look at a simple example of a Promise. The following Promise function fetches a list of user profiles from the database, where the result set is resolved by the promise function and the error is rejected.

const userProfileQuery = new Promise((resolve, reject) => {
  connection.query('SELECT * FROM Users', [], (err, result) => {
    if (err) reject({ type: 'SQL', err});
    connection.release();
    resolve(result);
});
userProfileQuery
  .then((data) => {
    const userList = data;
    // Do something crazy with the list
  })
  .catch((err) => {
    // Oh, snap!
    // Handle error
  });
Enter fullscreen mode Exit fullscreen mode

In an ideal world, we’d want to have a single try-catch block to handle all errors that occur in that single file.

const { getUserProfiles } = require('./helpers');
module.exports = () => {
  try {
    let userProfileList;
    getUserProfiles
      .then((data) => {
        userProfileList = data;
      })
      .catch((error) => {
        // Handle Promise Error
        // All errors thrown in this promise land here
      });
  } catch (error) {
    // Handle errors in this module
  }
}
Enter fullscreen mode Exit fullscreen mode

The above module is simple — It fetches a list of user profiles with the help of a Promise function.

But the problem with the above module is that when we throw a new Error inside the then block of the promise, it will always pass to the catch block of the promise. That is because throwing a new error inside a then block of a promise will always be passed to the catch block of the invoking promise function. This does not allow us to handle all errors in a module with a singular try-catch block.

But, alas! There is a way to handle this with the help of Async/Await. Let me explain this better with an example —

const { getUserProfiles } = require('./helpers');
module.exports = async () => {
  try {
    const userProfileList = await getUserProfiles;
  } catch (error) {
    // Handle errors in this module
    switch (type) {
      case ERROR_SQL:
        // Handle SQL errors
      default:
        // Handle common errors
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This little addition of async/await in your code does two things —
Assign the value to the variable which was resolved by the promise function.

Throw error if the promise function rejects anything.
Note that the value assignment works only when a promise functions resolves some value and errors get thrown only when the promise function rejects something.

This way, async/await lets us keep our code clean, maintainable, and easy to read.


Thanks for reading. If you have thoughts on this, be sure to leave a comment.

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (5)

Collapse
 
willemodendaal profile image
Willem Odendaal

Very useful, thank you :) I was just a bit confused by this statement, it seems to contradict itself?:

"But the problem with the above module is that when we throw a new Error inside the then block of the promise, it will always pass to the catch block of the promise. That is because throwing a new error inside a then block of a promise will always be passed to the catch block of the invoking promise function."

Collapse
 
fluffystark profile image
John Matt

I think the catch block it was referring to is the catch block right after the then block. But the goal was to have a single catch block do all the error handling but from that code, it had two catch blocks ( 1. for module errors, 2. for promise/then errors )

Collapse
 
gitfudge profile image
gitfudge • Edited

@fluffystark pretty much answers your question. However, I'll explain it further just in case -

Say you have a promise function called somePromise. When you do a throw new Error() inside your .then() block, like below

try {
    somePromise()
        .then((data) => {
            throw new Error('Inner error');
        })
        .catch((promiseError) => {
            console.log(promiseError); // Prints Inner error
        });
} catch (error) {
    // Handle all module errors here
}

the error will get passed to the .catch() block of somePromise() and not the catch block of the try-catch.

When having multiple promise statements in your code, it can become cumbersome to handle each promise's errors in its own .catch() block. Using async/await allows you to handle all errors in one place. I hope this cleared your doubt. :)

Collapse
 
toddhgardner profile image
Todd H. Gardner

Thanks for writing this! Using promises as a wrapper around logic that could fail is a useful pattern. You can also listen for promise errors globally (rather than attaching a catch to each one) using the unhandledrejection global event.

It’s also important to log these sort of failures back to a service like TrackJS to help you understand when things fail in production.

Collapse
 
azarouamine profile image
AzarouAmine

Somewhat off-topic, async/await were introduced in ES8 not ES6.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay