Comprehensive Guide to Error Handling with JavaScript Promises, async/await, and Node.js

This tutorial explains how to handle errors in JavaScript using Promise.reject/resolve, then/catch/finally, Promise.all, Promise.any, Promise.race, Promise.allSettled, async/await, async generators, and Node.js callback and EventEmitter patterns, providing clear code examples for each approach.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Comprehensive Guide to Error Handling with JavaScript Promises, async/await, and Node.js

Using Promise to Handle Errors

To demonstrate Promise handling, we start with a simple function that throws an exception when the argument is not a string.

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}

toUppercase(4);

Instead of throwing, we can return Promise.reject or Promise.resolve:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }
  const result = string.toUpperCase();
  return Promise.resolve(result);
}

Because the function now returns a Promise, we can use then to receive the result or catch to capture the error.

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));

The output is the error message "Wrong type given, expected a string".

Besides then and catch, Promise also provides finally, which works like the finally block in a try/catch statement.

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

Promise, Error, and Throw

Promise.reject

can be used to throw errors conveniently:

Promise.reject(TypeError("Wrong type given, expected a string"));

Alternatively, throwing an exception inside a Promise chain also rejects the Promise.

Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});

We catch the error with catch:

Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));

This pattern is common with fetch:

fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(json => console.log(json));

Handling Errors in Timers

Exceptions thrown inside a timer callback cannot be caught with a surrounding try/catch.

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}
// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

The solution is to wrap the asynchronous work in a Promise and reject it:

function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}

failAfterOneSecond().catch(reason => console.error(reason.message));

Using Promise.all for Error Handling

Promise.all(iterable)

resolves when all promises are fulfilled; if any promise rejects, the returned promise rejects with the first rejection reason.

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message));
// Output: No good, sorry!

Regardless of success or failure, finally runs:

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

Using Promise.any for Error Handling

Promise.any()

resolves as soon as any promise fulfills; if all reject, it rejects with an AggregateError containing all rejection reasons.

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

Using Promise.race for Error Handling

Promise.race(iterable)

settles as soon as the first promise settles, whether fulfilled or rejected.

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));
// Output: The first!

If a rejection appears first, the race rejects:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));
// Output: Ouch!

Using Promise.allSettled for Error Handling

Promise.allSettled()

returns a promise that resolves after all input promises settle, providing an array of objects describing each outcome.

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .finally(() => console.log("Always runs!"));

Using async/await for Error Handling

Marking a function with async makes it return a Promise, allowing the use of then, catch, and finally after the call.

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}

toUppercase("abc")
  .then(result => console.log(result))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

Within an async function, traditional try/catch/finally works as well:

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer();

Using async generators for Error Handling

Async generators produce Promises for each yield. Throwing inside the generator rejects the Promise.

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

const go = asyncGenerator();

go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.next().catch(reason => console.error(reason.message));

They can also be consumed with for await...of and wrapped in try/catch:

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();

Node.js Synchronous Error Handling

Synchronous errors in Node.js are handled with the usual try/catch/finally blocks.

Node.js Asynchronous Error Handling: Callback Pattern

Asynchronous Node APIs accept a callback where the first argument is an error object.

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // do stuff with the data
  });
}

Errors can be thrown, but they will crash the process; instead, forward the error to an error‑handling callback.

function errorHandler(error) {
  console.error(error.message);
  // log or report the error
}

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) return errorHandler(error);
    // do stuff with the data
  });
}

Node.js Asynchronous Error Handling: Event Emitters

Many Node modules inherit from EventEmitter, which emits an error event when something goes wrong.

const net = require("net");

const server = net.createServer().listen(80, "127.0.0.1");

server.on("listening", function() {
  console.log("Server listening!");
});

server.on("connection", function(socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

server.on("error", function(error) {
  console.error(error.message);
});

Summary

This guide covered various JavaScript error‑handling techniques, from simple synchronous try/catch to advanced asynchronous patterns using Promises, async/await, async generators, and Node.js callback and EventEmitter approaches.

Synchronous errors are the easiest to catch; asynchronous errors require careful use of Promise methods or async/await constructs.

Modern browser APIs favor Promise‑based patterns, making then/catch/finally or try/catch with async/await the preferred ways to handle exceptions.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Node.jsError Handlingasync/awaitPromise
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.