Node.js try catch for synchronous errors and async error handling

Node.js try catch is used to handle exceptions thrown while JavaScript code is running. When a statement inside the try block throws an exception, control moves to the matching catch block instead of stopping the Node.js process immediately.

Use try...catch for synchronous code such as JSON.parse(), validation logic, or synchronous file system calls. For asynchronous Node.js code, the correct pattern depends on how the operation is written: callbacks use the err argument, promises use .catch(), and async/await can use try...catch around the awaited promise.

This tutorial explains where Node.js try catch works, where it does not work, and how to handle errors in callback, promise, and async/await based code.

When Node.js try catch catches an error

A try...catch statement catches exceptions that are thrown during the same synchronous call stack. In simple terms, the error must happen while Node.js is still executing the code inside the try block.

Node.js code styleCan outer try catch handle it?Recommended error handling
Synchronous JavaScriptYestry...catch
fs.readFileSync()Yestry...catch
Callback-based fs.readFile()No, not with an outer try catchCheck the callback err argument
Promise chainNo, not without returning/awaiting the promise.catch()
async/awaitYes, when the awaited promise is inside the try blocktry { await ... } catch (err) { ... }

The JavaScript try...catch syntax is the same in Node.js as in browser JavaScript. The difference is that many Node.js APIs are asynchronous, so an error may be reported after the outer try block has already finished.

Example – Node.js Try Catch

In this example, we shall use a Try Catch around the piece of code that tries to read a file synchronously.

nodejs-try-catch-example.js

</>
Copy
# example for Node.js Try Catch
var fs = require('fs');

try{
	// file not presenet
	var data = fs.readFileSync('sample.html');
} catch (err){
	console.log(err);
}

console.log("Continuing with other statements..");

When the above program is run using node command, we will get the following output.

Output

arjun@arjun-VPCEH26EN:~/nodejs$ node nodejs-try-catch-example.js 
{ Error: ENOENT: no such file or directory, open 'sample.html'
    at Object.fs.openSync (fs.js:652:18)
    at Object.fs.readFileSync (fs.js:553:33)
    at Object.<anonymous> (/nodejs/nodejs-try-catch-example.js:5:16)
    at Module._compile (module.js:573:30)
    at Object.Module._extensions..js (module.js:584:10)
    at Module.load (module.js:507:32)
    at tryModuleLoad (module.js:470:12)
    at Function.Module._load (module.js:462:3)
    at Function.Module.runMain (module.js:609:10)
    at startup (bootstrap_node.js:158:16)
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'sample.html' }
Continuing with other statements..

Observe that the program did not terminate abruptly, but continued with the execution of subsequent statements.

For a current JavaScript file, use // for comments. The following version shows the same Node.js try catch flow with valid JavaScript comments and a smaller error message.

nodejs-try-catch-sync.js

</>
Copy
const fs = require('fs');

try {
  const data = fs.readFileSync('sample.html', 'utf8');
  console.log(data);
} catch (err) {
  console.log('Could not read file:', err.code);
}

console.log('Continuing with other statements...');

Output when sample.html does not exist

Could not read file: ENOENT
Continuing with other statements...

The important point is not the exact file error text. The important point is that readFileSync() throws before the next statement runs, so the catch block can handle it.

What happens without Node.js try catch for synchronous errors

Now we shall see what happens if we do not use try catch for the same operation above.

nodejs-try-catch-example-1.js

</>
Copy
# error without Node.js Try Catch
var fs = require('fs');

// try to read a file synchronously, file not presenet
var data = fs.readFileSync('sample.html');

console.log("Continuing with other statements..");

There is no error handling mechanism incorporated in the code. And the program terminates abruptly and the subsequent statements are not executed.

When the above program is run using node command, we will get the following output.

arjun@arjun-VPCEH26EN:~/nodejs$ node nodejs-try-catch-example-1.js 
/home/arjun/nodejs/nodejs-try-catch-example-1.js:1
(function (exports, require, module, __filename, __dirname) { # example for Node.js Try Catch
                                                              ^

SyntaxError: Invalid or unexpected token
    at createScript (vm.js:74:10)
    at Object.runInThisContext (vm.js:116:10)
    at Module._compile (module.js:537:28)
    at Object.Module._extensions..js (module.js:584:10)
    at Module.load (module.js:507:32)
    at tryModuleLoad (module.js:470:12)
    at Function.Module._load (module.js:462:3)
    at Function.Module.runMain (module.js:609:10)
    at startup (bootstrap_node.js:158:16)
    at bootstrap_node.js:598:3

The output above shows a syntax error because the first line starts with #, which is not a normal JavaScript comment. If that line is changed to a valid JavaScript comment, the missing file error would stop the script before the final console.log().

Corrected version without try catch

</>
Copy
const fs = require('fs');

// sample.html is not present
const data = fs.readFileSync('sample.html', 'utf8');

console.log('Continuing with other statements...');

Typical output

Error: ENOENT: no such file or directory, open 'sample.html'

Since the exception is not handled, Node.js reports the error and the statement after readFileSync() is not executed.

Why Node.js Try Catch should not be used for catching errors in Asynchronous Operations?

Consider the following example where we try to read a file asynchronously with a callback function and throw an error if something goes wrong. And we surround the task with a Try Catch Block, hoping to catch the error thrown.

nodejs-try-catch-example-2.js

</>
Copy
# Node.js Try Catch with Asynchronous Callback Function
var fs = require('fs');

try{
	fs.readFile('sample.txta',
		// callback function
		function(err, data) {		
			if (err) throw err;
	});
} catch(err){
	console.log("In Catch Block")
	console.log(err);
}
console.log("Next Statements")

Output

arjun@arjun-VPCEH26EN:~/nodejs/try-catch$ node nodejs-try-catch-example-2.js 
Next Statements
/home/arjun/nodejs/try-catch/nodejs-try-catch-example-2.js:8
			if (err) throw err;
			         ^

Error: ENOENT: no such file or directory, open 'sample.txta'

If you observe the output, console.log(“Next Statements”)  has been executed prior to if(err) throw err  which is because, reading file is being done asynchronously and the control does not wait the file operation to complete, instead it proceeds with the next statement. Which means, the control is out of try catch block. If an error occurs during the asynchronous operation, there is no try catch block the control could know of. Hence our Try Catch block cannot catch errors that could occur during asynchronous operations and developers should avoid catching errors thrown by asynchronous tasks with Node.js Try Catch blocks.

More precisely, an outer try...catch does not catch errors thrown later inside a callback. The callback runs on a later turn of the event loop, after the original try block has already completed. Throwing inside that callback can become an uncaught exception.

What happens to exceptions in asynchronous code ?

If you are puzzled as to what happens to exceptions in asynchronous code, here is the answer. If you have gone through Callback Function, and observed an error object as an argument, this is where any errors or exceptions are reported back to Node.js.

In Node.js callback APIs, the common convention is function(err, result). If err is not null or undefined, handle it inside the callback or pass it to a higher-level callback. Do not throw it and expect an outer try catch to catch it.

Correct Node.js callback error handling instead of throwing inside readFile

The safer callback version checks the err argument inside the callback. The error is handled where Node.js reports it.

nodejs-callback-error-handling.js

</>
Copy
const fs = require('fs');

fs.readFile('sample.txta', 'utf8', function (err, data) {
  if (err) {
    console.log('Could not read file:', err.code);
    return;
  }

  console.log(data);
});

console.log('Next statement');

Output when sample.txta does not exist

Next statement
Could not read file: ENOENT

The order of the output is expected. The last statement runs first because fs.readFile() starts the file operation and then returns. Later, the callback receives either the file data or an error.

Node.js promise error handling with catch

When a Node.js API returns a promise, use .catch() if you are writing a promise chain. The catch handler receives rejected promise errors.

nodejs-promise-catch.js

</>
Copy
const fs = require('fs/promises');

fs.readFile('sample.txta', 'utf8')
  .then(function (data) {
    console.log(data);
  })
  .catch(function (err) {
    console.log('Could not read file:', err.code);
  });

console.log('Next statement');

Output when sample.txta does not exist

Next statement
Could not read file: ENOENT

The promise rejection is handled by .catch(). This is different from catching a synchronous exception, but the purpose is the same: handle the failure without crashing the process.

Node.js try catch with async await

try...catch works well with async/await when the promise is awaited inside the try block. This is a common modern Node.js pattern because it keeps asynchronous code readable while still handling rejected promises.

nodejs-async-await-try-catch.js

</>
Copy
const fs = require('fs/promises');

async function readSampleFile() {
  try {
    const data = await fs.readFile('sample.txta', 'utf8');
    console.log(data);
  } catch (err) {
    console.log('Could not read file:', err.code);
  }

  console.log('Function completed');
}

readSampleFile();

Output when sample.txta does not exist

Could not read file: ENOENT
Function completed

Here, await fs.readFile(...) pauses the async function until the promise settles. If the promise rejects, control moves to catch.

Using finally with Node.js try catch

The optional finally block runs after try and catch, whether an error occurred or not. Use it for cleanup steps that must run in both success and failure cases.

nodejs-try-catch-finally.js

</>
Copy
function parseConfig(jsonText) {
  try {
    const config = JSON.parse(jsonText);
    console.log('Port:', config.port);
  } catch (err) {
    console.log('Invalid JSON:', err.message);
  } finally {
    console.log('Finished reading configuration');
  }
}

parseConfig('{ "port": 3000 }');
parseConfig('{ port: 3000 }');

Output

Port: 3000
Finished reading configuration
Invalid JSON: Expected property name or '}' in JSON at position 2
Finished reading configuration

The exact error message for invalid JSON can vary by Node.js version, but finally runs in both calls.

Common Node.js try catch mistakes to avoid

  • Do not wrap callback-based async code in an outer try catch and assume it handles callback errors. Check the callback err argument instead.
  • Do not leave the catch block empty. At least log, return a safe response, or pass the error to a central handler.
  • Do not catch errors too broadly without context. A catch block should make it clear which operation failed.
  • Do not expose raw error details to users in production responses. Log useful details for developers, but return safe messages to clients.
  • Do not use try catch as a replacement for validation. Validate known inputs before running operations that can fail.

Node.js try catch quick syntax reference

The basic syntax has one try block, one catch block, and an optional finally block.

</>
Copy
try {
  // code that may throw an exception
} catch (err) {
  // handle the exception
} finally {
  // optional cleanup code
}

For deeper JavaScript syntax details, refer to the MDN reference for try…catch. For Node.js code, also choose the error pattern that matches the API style you are using.

Node.js try catch FAQs

Can Node.js try catch catch errors from fs.readFile?

An outer try catch cannot catch errors reported later by callback-based fs.readFile(). Check the callback err argument. If you use fs/promises with await, then a try catch around the awaited call can catch the rejected promise.

Should I use try catch around every Node.js function?

No. Use try catch around operations that can throw and where you can handle the failure meaningfully. For callbacks, promises, streams, and request handlers, use the error handling pattern expected by that API or framework.

Why does code after an asynchronous Node.js call run before the catch block?

The asynchronous call starts the operation and returns immediately. The callback or promise handler runs later. An outer synchronous try catch has already finished by that time, so it cannot catch a later callback throw.

Can I use Node.js try catch with async await?

Yes. Put the await expression inside the try block. If the awaited promise rejects, control moves to the catch block.

What should I do inside a Node.js catch block?

Handle the error in a way that fits the program: log useful developer details, return a safe response, retry only when appropriate, release resources, or pass the error to a central error handler.

QA checklist for a Node.js try catch implementation

  • Confirm whether the code is synchronous, callback-based, promise-based, or written with async/await.
  • Use try...catch for synchronous throws and awaited promise rejections, not for errors thrown later inside callbacks.
  • For Node.js callbacks, check err before using the result data.
  • For promises, add .catch() or await the promise inside a try block.
  • Make catch messages useful enough for debugging without exposing sensitive details to end users.

Node.js try catch key takeaways

In this Node.js TutorialNode.js Try Catch, we have learnt the scenarios where to use Try Catch, and where not to use it.

Use Node.js try catch for synchronous exceptions and for async/await code when the awaited promise is inside the try block. For callback-based asynchronous APIs, handle the err argument inside the callback. For promise chains, use .catch(). Choosing the correct pattern keeps error handling predictable and prevents uncaught exceptions.