An error is an object with a description denoting that something went wrong. Whether you want to handle runtime or syntax errors, this tutorial will help you out.
The tutorial simplifies Node.js error handling in synchronous and asynchronous (callback and promised-based) code using Node.js process
module, try-catch block, and then-catch block.
It would be helpful to acquire basic Node.js knowledge, understand promises and install the latest version of Node.js and Visual Studio Code before proceeding with this tutorial.
Let's get started.
Lab setup to practice Node.js error handling
You can open the file using your preferred code editor in the examples section. I am using Visual Studio Code on Ubuntu.
Make the project directory and cd
into it.
mkdir errorHandling && cd errorHandling
Next, create sync.js, callback.js, and promises.js for handling errors related to the synchronous, callback, and promise-based asynchronous code, respectively.
touch sync.js callback.js promises.js
code .
Example~1: Node.js error handling in synchronous code
A try-catch block is the most typical way for Node.js error handling in synchronous code. For example, assume we try reading a file that does not exist.
Input
const fs = require("fs")
try {
const output = fs.readFileSync('index.html', 'utf-8')
console.log(output)
} catch {
console.log("There was an error reading the file.")
}
We import the fs
module. In thetry
block, we attempt to read the file using the fs.readFileSync()
method. And console-log the output. If the process fails, it throws an error object. The catch
block receives the error object through the given parameter. We could print the error object, but we printed a custom message, "There was an error reading the file."
Using the node
command, run the file on the terminal.
node sync.js
Output
We get the expected error.
$ node sync.js
There was an error reading the file.
Now, let's print the error object.
Input
const fs = require("fs")
try {
const output = fs.readFileSync('index.html', 'utf-8')
console.log(output)
} catch (error) {
console.log(error)
}
Output
$ node sync.js
Error: ENOENT: no such file or directory, open 'index.html'
at Object.openSync (node:fs:599:3)
at Object.readFileSync (node:fs:467:35)
at Object.<anonymous> (/home/user/errorHandling/sync.js:4:23)
at Module._compile (node:internal/modules/cjs/loader:1119:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
at Module.load (node:internal/modules/cjs/loader:997:32)
at Module._load (node:internal/modules/cjs/loader:838:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:18:47 {
errno: -2,
syscall: 'open',
code: 'ENOENT',
path: 'index.html'
}
The following line tells us that the program caught an error object denoting that the requested file does not exist.
Error: ENOENT: no such file or directory, open 'index.html'
The program gives more hints about the error, like its code and the requested file path.
Example~2: Node.js error handling using a callback function + process module
Most Node.js native modules accommodate asynchronous programming through a callback function. The first parameter to the callback function is the error object.
Use a callback function + process module
Let's create the index.html
file that did not exist in Example~1
cat >> index.html
Add some content to it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
Then, attempt to read its content asynchronously with a misspelled name.
Input
const fs = require("fs")
fs.readFile('endix.html', 'utf-8', (error, data) => {
if (error) throw error
console.log(data)
})
Unless the error is handled, the throw keyword halts the execution of subsequent code portions. We can handle the error using the process
module.
process.on("uncaughtException", error => error ? console.log(error) : console.log(""))
The process.on()
method listens for the uncaughtException
event. It then prints the error or an empty string.
Output
$ node callback.js
[Error: ENOENT: no such file or directory, open 'endix.html'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: 'endix.html'
}
Alternatively, we can do some computations and handle the custom error with a callback function.
Use a callback function only
Assume we want to throw an error object whenever a user attempts to divide a whole number with zero.
Input
const calculate = (x, y, callback) =>
y === 0
?
callback(new Error("Fatal error: Attempting to divide by zero"))
:
callback(null, x/y)
calculate(3, 0, (err, result) => err ? console.log(err) : console.log(result))
The calculate()
function accepts the first number x
, the second number y
, and a callback function. If y
is zero, we throw a custom error object with the message, "Fatal error: Attempting to divide by zero". Otherwise, the callback function returns no error and the result of dividing x
with y
.
We then attempt to divide 3 by 0 and get the custom error object.
Output
$ node callback.js
Error: Fatal error: Attempting to divide by zero
at calculate (/home/user/errorHandling/callback.js:4:10)
at Object.<anonymous> (/home/user/errorHandling/callback.js:8:1)
at Module._compile (node:internal/modules/cjs/loader:1119:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
at Module.load (node:internal/modules/cjs/loader:997:32)
at Module._load (node:internal/modules/cjs/loader:838:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:18:47
Example~3: Node.js error handling in promises
We can handle promise-based errors using then-catch or try-catch blocks in an async-await function.
const returnAPromise = () => {
return new Promise( (resolve, reject) => {
let didSomething = false
didSomething ? resolve('OK') : reject('Mission failed')
})
}
// then-catch
returnAPromise()
.then( result => console.log(result))
.catch( error => console.log(error))
// async-await and try-catch
const showOutput = async () => {
try {
const data = await returnAPromise()
console.log(data)
} catch (error) {
console.log(error)
}
}
showOutput()
The returnAPromise()
function returns a promise. If the didSomething
condition is met, it resolves the promise with the OK message. Otherwise, it rejects the promise with a Mission failed message.
The then()
method prints the success message. On the other hand, the catch(
) method handles errors that the then
block(s) may throw. Finally, it prints the error message. The then-catch methods work similarly to try-catch in async-await functions.
Output
$ node promises.js
Mission failed
Mission failed
The first Mission failed originated from the then-catch block, whereas the second Mission failed resulted from the try-catch block.
Additionally, we can throw a custom error inside the async-await function and handle it with the then-catch block. Assume we misspell a part of the URL when requesting remote data with the fetch API.
Input
const getUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/userss")
if (res.status !== 200) throw new Error("Failed to get users")
const data = await res.json()
return data
}
getUsers()
.then( users => console.log(users))
.catch( error => console.log(error))
Although the target URL is https://jsonplaceholder.typicode.com/users, we accidentally input https://jsonplaceholder.typicode.com/userss. We check whether the request is successful. Otherwise, we throw a custom error.
if (res.status !== 200) throw new Error("Failed to get users")
The program will not attempt to retrieve users from the response object if it encounters an error. Instead, the promise fails, throwing the error. We then handle the error in the catch
block.
.catch(error => console.log(error))
Output
$ node promises.js
(node:2846) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Error: Failed to get users
at getUsers (/home/user/errorHandling/promises.js:3:35)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Conclusion
In this tutorial, you learned Node.js error handling in synchronous and asynchronous code using practical examples. Now that you have multiple options to choose from when handling errors, it is your turn to apply the most applicable approach.