Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Errors in asynchronous code

You know how try...catch and promises work from the tutorial. In Node.js errors in asynchronous code have an important consequence: a promise that failed with an error nobody caught — an unhandled rejection — stops the whole program. For a server this means the site stops opening for everyone.

try...catch around await

An error from await is caught with an ordinary try...catch:

import { readFile } from 'node:fs/promises';
 
async function loadPosts() {
  try {
    const text = await readFile('posts.json', 'utf8');
    return JSON.parse(text);
  } catch (error) {
    console.log(`Could not load posts: ${error.code}`);
    return [];
  }
}
 
console.log(await loadPosts());

The file does not exist, so "Could not load posts: ENOENT" and an empty array are printed.

A forgotten await

try...catch catches a promise error only when the promise is awaited inside the try block:

// app.js
import { readFile } from 'node:fs/promises';
 
try {
  readFile('posts.json', 'utf8'); // await forgotten
} catch {
  console.log('We never get here');
}

The catch block does not run: the promise rejects later, after try has finished. Node.js prints the ENOENT error and exits with code 1.

Errors in callbacks

In functions called by a timer or an event, an outer try...catch is powerless too — it finished long before the call. Catch the error inside the function itself:

setTimeout(() => {
  try {
    JSON.parse('{ broken json');
  } catch (error) {
    console.log(`Error in the timer: ${error.name}`);
  }
}, 10);

It prints "Error in the timer: SyntaxError".

The last line of defence

The unhandledRejection event of process fires for a rejection nobody handled. There you log the error and end the program with an error code:

// src/server.js
process.on('unhandledRejection', (reason) => {
  console.error('Unhandled error:', reason);
  process.exit(1);
});

This is not a way to silence errors but a fallback that makes sure the error reaches the log. The server is then restarted — the deployment lesson shows how a process manager does that. The similar event for synchronous errors is uncaughtException.

Errors in parallel tasks

Promise.all() rejects on the first error. If you need the results of all tasks, use Promise.allSettled():

const results = await Promise.allSettled([
  Promise.resolve('post 1'),
  Promise.reject(new Error('post 2 not found')),
]);
 
for (const result of results) {
  console.log(result.status === 'fulfilled' ? result.value : result.reason.message);
}

It prints "post 1" and "post 2 not found".

Common mistake

Inside forEach with an async function, errors and await get lost: forEach does not wait for promises. Write a for...of loop:

// app.js
for (const id of [1, 2, 3]) {
  await publishPost(id); // an error reaches the outer try...catch
}

This way tasks run one after another, and any error bubbles up to where it can be caught.

Thanks for reading!

Donate to the site — we work hard for you.