Handling Errors in Node (asynchronous)
Tags: node, javascript
Published: 2021-08-04 22:39:46

---
title: Handling Errors in Node (asynchronous)
published: true
date: 2021-08-01 00:00:00 UTC
tags: node, javascript
canonical_url: https://www.mariokandut.com/handling-errors-in-asynchronous-functions-node-js/
---

Building robust Node.js applications requires dealing with errors in proper way. This is the third article of a series and aims to give an overview on how to handle errors in async scenarios Node.js.

- [Introduction to Errors in Node.js](https://www.mariokandut.com/introduction-to-errors-in-node-js/)
- [Handling errors in synchronous scenarios in Node.js](https://www.mariokandut.com/handling-errors-in-synchronous-functions-node-js)
- Handling errors in asynchronous scenarios in Node.js (this article)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#handling-errors-in-asynchronous-scenarios)Handling Errors in Asynchronous Scenarios

In the previous article we looked at error handling in sync scenarios, where errors are handled with `try/catch` blocks when an error is throw using the `throw` keyword. Asynchronous syntax and patterns are focussed on callbacks, Promise abstractions and the `async/await` syntax.

There are three ways to handle errors in async scenarios (not mutually inclusive):

- Rejection
- Try/Catch
- Propagation

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#rejection)Rejection

So, when an error occurs in a synchronous function it's an exception, but when an error occurs in a `Promise` its an asynchronous error or a promise rejection. Basically, exceptions are synchronous errors and rejections are asynchronous errors.

Let's go back to our `divideByTwo()` function and convert it to return a promise:

```
function divideByTwo(amount) {
  return new Promise((resolve, reject) => {
    if (typeof amount !== 'number') {
      reject(new TypeError('amount must be a number'));
      return;
    }
    if (amount <= 0) {
      reject(new RangeError('amount must be greater than zero'));
      return;
    }
    if (amount % 2) {
      reject(new OddError('amount'));
      return;
    }
    resolve(amount / 2);
  });
}

divideByTwo(3);
```

The promise is created using the `Promise` constructor. The function passed to the `Promise` is called **tether function** , it takes two arguments `resolve` and `reject`. When the operation is successfully, `resolve` is called, and in case of an error `reject` is called. The error is passed into `reject` for each error case so that the promise will reject on invalid input.

When running the above code the output will be:

```
(node:44616) UnhandledPromiseRejectionWarning: OddError [ERR_MUST_BE_EVEN]: amount must be even

# ... stack trace
```

The rejection is unhandled, because a `Promise` must use the `catch` method to catch rejections. Read more about Promises in the article [Understanding Promises in Node.js](https://www.mariokandut.com/understanding-promises-in-node-js/).

Let's modify the `divideByTwo` function to use handlers:

```
divideByTwo(3)
  .then(result => {
    console.log('result', result);
  })
  .catch(err => {
    if (err.code === 'ERR_AMOUNT_MUST_BE_NUMBER') {
      console.error('wrong type');
    } else if (err.code === 'ERRO_AMOUNT_MUST_EXCEED_ZERO') {
      console.error('out of range');
    } else if (err.code === 'ERR_MUST_BE_EVEN') {
      console.error('cannot be odd');
    } else {
      console.error('Unknown error', err);
    }
  });
```

The functionality is now the same as in the [synchronous non-promise based code](https://www.mariokandut.com/handling-errors-in-synchronous-functions-node-js)) in the previous article.

When a `throw` appears inside a promise handler, it won't be an error, instead it will be a rejection. The `then` and `catch` handler will return a new promise that rejects as a result of the `throw` within the handler.

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#async-trycatch)Async Try/Catch

The `async/await` syntax supports `try/catch` of rejections, which means that `try/catch` can be used on asynchronous promise-based APIs instead of the `then` and `catch` handlers.

Let's convert the example code to use the `try/catch` pattern:

```
async function run() {
  try {
    const result = await divideByTwo(1);
    console.log('result', result);
  } catch (err) {
    if (err.code === 'ERR_AMOUNT_MUST_BE_NUMBER') {
      console.error('wrong type');
    } else if (err.code === 'ERR_AMOUNT_MUST_EXCEED_ZERO') {
      console.error('out of range');
    } else if (err.code === 'ERR_MUST_BE_EVEN') {
      console.error('cannot be odd');
    } else {
      console.error('Unknown error', err);
    }
  }
}

run();
```

The only difference between the synchronous handling is the wrapping in an async function and calling `divideByTwo()` with `await`, so that the async function can handle the promise automatically.

Using an `async` function with `try/catch` around an awaited promise is syntactic sugar. The `catch` block is basically the same as the `catch` handler. An async function always returns a promise that resolves unless a rejection occurs. This also would mean we can convert the `divideByTwo` function from returning a promise to simply throw again. Essentially the code is the synchronous version with the `async` keyword.

```
async function divideByTwo(amount) {
  if (typeof amount !== 'number')
    throw new TypeError('amount must be a number');
  if (amount <= 0)
    throw new RangeError('amount must be greater than zero');
  if (amount % 2) throw new OddError('amount');
  return amount / 2;
}
```

The above code has the same functionality as the synchronous version, but now we can perform other asynchronous tasks, like fetching some data or writing a file.

The errors in all of these examples are developer errors. In an asynchronous context operation errors are more likely to encounter. For example, a POST request fails for some reason, and the data couldn't have been written to the database. The pattern for handling operational errors is the same. We can `await` an async operation and catch any errors and handle accordingly (send request again, return error message, do something else, etc.).

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#propagation)Propagation

Another way of handling errors is propagation. Error propagation is where, instead of handling the error where it occurs, the caller is responsible for error handling. When using `async/await` functions, and we want to propagate an error we simply rethrow it.

Let's refactor the function to propagate unknown errors:

```
class OddError extends Error {
  constructor(varName = '') {
    super(varName + ' must be even');
    this.code = 'ERR_MUST_BE_EVEN';
  }
  get name() {
    return 'OddError [' + this.code + ']';
  }
}

function codify(err, code) {
  err.code = code;
  return err;
}

async function divideByTwo(amount) {
  if (typeof amount !== 'number')
    throw codify(
      new TypeError('amount must be a number'),
      'ERR_AMOUNT_MUST_BE_NUMBER',
    );
  if (amount <= 0)
    throw codify(
      new RangeError('amount must be greater than zero'),
      'ERR_AMOUNT_MUST_EXCEED_ZERO',
    );
  if (amount % 2) throw new OddError('amount');
  // uncomment next line to see error propagation
  // throw Error('propagate - some other error');;
  return amount / 2;
}

async function run() {
  try {
    const result = await divideByTwo(4);
    console.log('result', result);
  } catch (err) {
    if (err.code === 'ERR_AMOUNT_MUST_BE_NUMBER') {
      throw Error('wrong type');
    } else if (err.code === 'ERRO_AMOUNT_MUST_EXCEED_ZERO') {
      throw Error('out of range');
    } else if (err.code === 'ERR_MUST_BE_EVEN') {
      throw Error('cannot be odd');
    } else {
      throw err;
    }
  }
}
run().catch(err => {
  console.error('Error caught', err);
});
```

Unknown errors are propagated from the `divideByTwo()` function, to the `catch` block and then up to the `run` function with the `catch` handler. Try to run the code after uncommenting the `throw Error('some other error');` in the `divideByTwo()` function to unconditionally throw an error. The output will be something like this: `Error caught Error: propagate - some other error`.

If and when an error is propagated depends highly on the context. A reason to propagate an error might be when error handling strategies have failed at a certain level. An example would a failed network request, which was retried for several times before propagating.

In general, try to propagate errors for handling at the highest level possible. This would be the main file in a module, and in an application the entry point file.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#tldr)TL;DR

- Exceptions are synchronous errors and rejections are asynchronous errors.
- A promise rejection has to be handled. The `catch` handler handles the promise rejection.
- There are three ways to handle errors in async scenarios: Rejection, Try/Catch and Propagation
- The `async/await` syntax supports `try/catch` of rejections.
- `try/catch` can be used on asynchronous promise-based APIs instead of the `then` and `catch` handlers.
- Error propagation is where, instead of handling the error where it occurs, the caller is responsible for error handling.
- Error propagation depends on the context. When propagated it should be to the highest level possible.

Thanks for reading and if you have any **questions** , use the **comment** function or **send me a message** **[@mariokandut](https://www.twitter.com/mariokandut)**.

If you want to know more about **[Node](https://www.mariokandut.com/tags/node)**, have a look at these [Node Tutorials](https://www.mariokandut.com/tags/node).

**References (and Big thanks):**

[JSNAD](https://trainingportal.linuxfoundation.org/learn/course/nodejs-application-development-lfw211/handling-errors/handling-errors),[MDN Errors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error),[MDN throw](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw),[Node.js Error Codes](https://nodejs.org/api/errors.html#errors_node_js_error_codes),[Joyent](https://www.joyent.com/node-js/production/design/errors)