Error Handling
Error handling is how a Node.js program detects, reports, and recovers from things going wrong — using try/catch for synchronous and async/await code, error-first callbacks, and the Error object to carry details.
Learn Error Handling in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll know how to throw and catch errors, how to handle failures in promises and callbacks (and the trap where try/catch silently misses them), and how to define your own error types and add a process-level safety net so an unexpected failure doesn't take your whole app down without a trace.
What You'll Learn in This Lesson
1️⃣ try / catch / finally and throwing
When something goes wrong you throw an Error object . Throwing immediately stops the current function and jumps to the nearest catch . You wrap risky code in a try block, handle the failure in catch (err) , and put cleanup that must always run in finally .
Read the worked example below — every line is explained — then run it and compare with the expected output.
2️⃣ Handling Errors in Async Code
Asynchronous code fails in a few different shapes, and the way you catch it depends on the shape. The big trap for beginners: a plain try/catch only catches what happens while it is running , so an error thrown later inside a callback slips right past it.
Read the comments carefully — example 4 shows exactly why the outer catch never fires.
Your turn. The program below works once you fill in the three blanks marked ___ . Follow the 👉 hints, then run it and compare with the expected output.
3️⃣ Custom Errors & Process-Level Safety Nets
For bigger programs you'll want to tell error types apart. A custom error class extends the built-in Error , so you can check err instanceof ValidationError and attach extra fields like which input failed. And for the failures that escape every handler, Node gives you two last-resort process events.
Treat the process handlers as smoke detectors: log the failure, shut down gracefully, and let a process manager restart you. They are a safety net, not your everyday error handling.
No blanks this time — just a brief and an outline to keep you on track. Write it yourself, run it, and check your output against the example in the comments. Parsing untrusted input is exactly where real apps crash, so this is a habit worth building.
📋 Quick Reference — Error Handling
Practice quiz
When you throw inside a try block, where does execution jump?
- To the nearest catch block, skipping the rest of the try
- To the end of the file
- Back to the start of the try
- To the finally block only, never catch
Answer: To the nearest catch block, skipping the rest of the try. throw immediately stops the current function and jumps to the nearest catch.
When does a finally block run?
- Only when the try succeeds
- Only when the try throws
- Always, whether the try succeeded or threw
- Only if there is no catch
Answer: Always, whether the try succeeded or threw. finally always runs after try/catch, making it ideal for cleanup like closing files.
Does a plain try/catch catch an error thrown inside a setTimeout callback?
- Yes, always
- No, because the callback runs after the try has already exited
- Only if the delay is 0
- Only with await
Answer: No, because the callback runs after the try has already exited. The callback runs later, after the try block finished, so the outer catch never sees the throw.
How do you catch a rejected promise when using async/await?
- Attach .then() only
- Wrap the await in a try/catch block
- Use process.exit()
- Ignore it; await never rejects
Answer: Wrap the await in a try/catch block. await re-throws a rejected promise at that line, so a surrounding try/catch catches it.
In an error-first callback (err, result), what should you do first?
- Use result before checking err
- Throw the result
- Log result and return
- Check err and return early if it is set
Answer: Check err and return early if it is set. Check the first argument first: if (err) return handleIt(err), only then use the result.
Why should you throw an Error object rather than a string?
- Strings are slower to throw
- Error objects carry a message and a stack trace that tools expect
- You cannot throw a string in Node
- Strings always crash the process
Answer: Error objects carry a message and a stack trace that tools expect. Error objects carry message and stack; a thrown string loses the stack and breaks err.message readers.
How does a custom error class let callers recognize a specific failure?
- By matching the message text exactly
- By reading a global variable
- By checking err instanceof ValidationError
- By comparing line numbers
Answer: By checking err instanceof ValidationError. Extending Error lets callers branch on instanceof instead of fragile message-string matching.
What fires when a rejected promise has no .catch() and no surrounding try/catch?
- process.on('unhandledRejection')
- process.on('uncaughtException')
- process.on('exit')
- process.on('SIGTERM')
Answer: process.on('unhandledRejection'). unhandledRejection fires for a rejected promise nobody handled; uncaughtException is for sync escapes.
What is the right way to use uncaughtException and unhandledRejection handlers?
- As your everyday error handling for all logic
- To silently ignore every error
- Only to log the failure and shut down, since the process is in an unknown state
- To retry the failed operation forever
Answer: Only to log the failure and shut down, since the process is in an unknown state. They are last-resort safety nets: log, shut down gracefully, and let the process restart.
What is wrong with an empty catch block like catch (err) {}?
- It is a syntax error
- It re-throws automatically
- It speeds up the program
- It swallows the failure silently and makes bugs impossible to trace
Answer: It swallows the failure silently and makes bugs impossible to trace. Swallowing errors hides failures; at minimum log err.message, and usually handle or re-throw.