Bird
Raised Fist0
Node.jsframework~15 mins

Unhandled rejection handling in Node.js - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Unhandled rejection handling
What is it?
Unhandled rejection handling is the process of managing errors that happen when a promise in Node.js is rejected but no code catches that rejection. Promises are ways to handle tasks that finish later, like reading a file or asking a server. If a promise fails and no one handles the failure, it causes an unhandled rejection. Handling these rejections helps keep programs stable and prevents crashes.
Why it matters
Without handling unhandled rejections, your Node.js program might crash unexpectedly or behave unpredictably. This can cause downtime, lost data, or security risks. Proper handling ensures your app stays reliable and you can fix problems quickly. It’s like having a safety net for mistakes that happen later in your code.
Where it fits
Before learning this, you should understand JavaScript promises and basic error handling with try/catch. After this, you can learn about advanced error monitoring, logging tools, and graceful shutdowns in Node.js applications.
Mental Model
Core Idea
Unhandled rejection handling catches promise errors that no one else caught, preventing crashes and allowing graceful error management.
Think of it like...
Imagine you throw a ball to a friend, expecting them to catch it. If they don’t catch it and no one notices, the ball hits the ground and causes trouble. Handling unhandled rejections is like having a safety net that catches any balls dropped by accident.
┌───────────────┐
│ Promise runs  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Promise rejects│
└──────┬────────┘
       │
       ▼
┌─────────────────────────────┐
│ Is rejection handled by code?│
└──────┬───────────────┬───────┘
       │ Yes           │ No    
       ▼               ▼       
┌───────────────┐  ┌───────────────┐
│ Normal error  │  │ Unhandled     │
│ handling      │  │ rejection     │
└───────────────┘  └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Promises and Rejections
🤔
Concept: Learn what promises are and how they can be rejected when something goes wrong.
A promise is a JavaScript object representing a task that will finish in the future. It can either succeed (resolve) or fail (reject). For example, reading a file might fail if the file doesn't exist, causing the promise to reject.
Result
You understand that promises can fail and that failure is called rejection.
Knowing that promises can reject is the first step to understanding why unhandled rejections happen.
2
FoundationBasic Error Handling with Promises
🤔
Concept: Learn how to catch errors from promises using .catch() or try/catch with async/await.
You can handle promise errors by adding a .catch() method after the promise or using try/catch blocks with async functions. This prevents errors from going unnoticed.
Result
You can prevent unhandled rejections by catching errors properly.
Handling promise errors explicitly stops unhandled rejections and keeps your app stable.
3
IntermediateWhat Happens When Rejections Are Unhandled?
🤔Before reading on: do you think unhandled rejections crash the Node.js process immediately or just log a warning? Commit to your answer.
Concept: Explore the default behavior of Node.js when a promise rejection is not handled.
If a promise rejects and no .catch() or try/catch handles it, Node.js emits an 'unhandledRejection' event. Depending on the Node.js version and settings, it may log a warning or crash the process.
Result
You see that unhandled rejections can cause warnings or crashes, which can disrupt your app.
Understanding Node.js default behavior helps you realize why handling unhandled rejections is critical for app reliability.
4
IntermediateUsing process.on('unhandledRejection')
🤔Before reading on: do you think listening to 'unhandledRejection' lets you fix errors or just logs them? Commit to your answer.
Concept: Learn how to catch all unhandled rejections globally using Node.js event listeners.
Node.js allows you to listen for unhandled rejections with process.on('unhandledRejection', handler). This handler receives the error and promise, letting you log, clean up, or shut down gracefully.
Result
You can catch all unhandled rejections in one place and respond appropriately.
Global handling is a safety net that prevents crashes and helps maintain control over unexpected errors.
5
IntermediateDifferences Between Unhandled Rejection and RejectionHandled
🤔Before reading on: do you think a rejection can be handled after being unhandled? Commit to your answer.
Concept: Understand that a rejection might be unhandled at first but handled later, and Node.js emits 'rejectionHandled' events.
Sometimes, a promise rejection is not caught immediately but a handler is added later. Node.js emits 'rejectionHandled' to notify that the rejection is now handled, which can help debugging.
Result
You know that unhandled rejection is not always permanent and can be fixed dynamically.
Knowing this helps avoid false alarms and better manage asynchronous error handling.
6
AdvancedBest Practices for Production Unhandled Rejection Handling
🤔Before reading on: should you always keep your app running after an unhandled rejection? Commit to your answer.
Concept: Learn how to handle unhandled rejections safely in production, including logging and graceful shutdown.
In production, it's best to log unhandled rejections with details, alert developers, and then shut down the app gracefully to avoid inconsistent states. Restart mechanisms can bring the app back up safely.
Result
Your app handles errors robustly without silent failures or corrupted states.
Understanding that some errors require stopping the app prevents hidden bugs and data loss.
7
ExpertInternal Node.js Handling and Future Changes
🤔Before reading on: do you think Node.js will keep allowing unhandled rejections without crashing forever? Commit to your answer.
Concept: Explore how Node.js internally tracks unhandled rejections and the evolving policies to treat them as fatal errors.
Node.js tracks promises and their rejection handlers internally. Recent versions treat unhandled rejections as fatal by default, crashing the process to force developers to fix errors. This policy is evolving to improve app safety.
Result
You understand why unhandled rejections are treated strictly and how Node.js enforces this.
Knowing Node.js internals and future directions helps you write forward-compatible, safer code.
Under the Hood
Node.js uses an internal promise tracking system that monitors when promises reject and whether their rejection handlers are attached. When a rejection occurs without a handler, Node.js emits an 'unhandledRejection' event. If the rejection remains unhandled after a short delay, Node.js may log a warning or terminate the process depending on the version and settings. This mechanism ensures developers are alerted to missed errors that could destabilize the app.
Why designed this way?
This design balances developer convenience and app safety. Early Node.js versions ignored unhandled rejections, causing silent bugs. Later, warnings were added, then the option to crash the process to force fixes. The delay before crashing allows handlers to be added asynchronously, avoiding false positives. This approach evolved from community feedback and real-world failures to improve reliability.
┌───────────────┐
│ Promise created│
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Promise rejects│
└──────┬────────┘
       │
       ▼
┌───────────────────────────────┐
│ Check if rejection handler set │
└──────┬───────────────┬────────┘
       │ Yes           │ No     
       ▼               ▼        
┌───────────────┐  ┌─────────────────────┐
│ Normal error  │  │ Emit 'unhandledRejection'│
│ handling      │  └─────────────┬───────────┘
└───────────────┘                │
                                ▼
                     ┌─────────────────────────┐
                     │ Wait for possible handler│
                     └─────────────┬───────────┘
                                   │
                      Handler added│No handler added
                                   │
                                   ▼
                     ┌─────────────────────────┐
                     │ Emit 'rejectionHandled' │
                     │ or crash process         │
                     └─────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: do you think unhandled rejections always crash the Node.js process immediately? Commit to yes or no.
Common Belief:Unhandled promise rejections always crash the Node.js process right away.
Tap to reveal reality
Reality:Node.js may only log a warning or emit an event initially; crashing depends on version and settings.
Why it matters:Assuming immediate crash leads to unnecessary panic or ignoring warnings, causing hidden bugs.
Quick: do you think adding a .catch() after some delay still handles a rejection? Commit to yes or no.
Common Belief:If a promise rejection is not caught immediately, it can never be handled later.
Tap to reveal reality
Reality:Node.js allows handlers to be added after rejection and emits 'rejectionHandled' to signal this.
Why it matters:Believing otherwise can cause premature error handling or false alarms.
Quick: do you think process.on('unhandledRejection') can fix errors and keep the app running safely? Commit to yes or no.
Common Belief:Listening to 'unhandledRejection' lets you fix errors and safely continue running the app.
Tap to reveal reality
Reality:This listener is mainly for logging and cleanup; continuing without restart risks unstable state.
Why it matters:Ignoring this can cause corrupted data or unpredictable behavior in production.
Quick: do you think unhandled rejection handling is only useful during development? Commit to yes or no.
Common Belief:Unhandled rejection handling is only important when developing, not in production.
Tap to reveal reality
Reality:Proper handling is critical in production to avoid crashes, data loss, and security issues.
Why it matters:Neglecting this in production leads to serious reliability problems and user impact.
Expert Zone
1
Unhandled rejection events are asynchronous and may be emitted after the rejection happens, so timing matters when adding handlers.
2
Node.js allows configuring unhandled rejection behavior via command-line flags and environment variables, enabling different policies per environment.
3
Some libraries internally catch and rethrow errors, which can mask unhandled rejections if not carefully monitored.
When NOT to use
Avoid relying solely on process-level unhandled rejection handlers for error control. Instead, use explicit promise error handling and structured error boundaries. For critical systems, consider synchronous error handling or alternative async patterns like callbacks with error-first conventions.
Production Patterns
In production, developers use centralized logging in the 'unhandledRejection' handler, alerting systems, and graceful shutdown sequences. They combine this with process managers like PM2 or Docker restart policies to automatically recover from crashes caused by unhandled rejections.
Connections
Exception Handling in Synchronous Code
Unhandled rejection handling is the asynchronous counterpart to try/catch exception handling in synchronous code.
Understanding synchronous exceptions helps grasp why promises need their own error handling mechanisms.
Event-Driven Architecture
Unhandled rejection events are part of Node.js's event-driven model, where errors are emitted as events to be handled asynchronously.
Knowing event-driven design clarifies how Node.js manages errors without blocking execution.
Safety Nets in Aviation
Unhandled rejection handling acts like safety nets or backup systems in aviation that catch failures before they cause disasters.
Recognizing this parallel highlights the importance of fallback mechanisms in complex systems.
Common Pitfalls
#1Ignoring unhandled rejections and assuming the app will keep running fine.
Wrong approach:Promise.reject(new Error('fail')); // no .catch or handler // No process.on('unhandledRejection') listener
Correct approach:Promise.reject(new Error('fail')).catch(err => console.error(err)); process.on('unhandledRejection', err => { console.error('Unhandled rejection:', err); process.exit(1); // graceful shutdown });
Root cause:Misunderstanding that unhandled rejections can crash or destabilize the app if not caught.
#2Using process.on('unhandledRejection') to silently ignore errors and continue running.
Wrong approach:process.on('unhandledRejection', err => { console.log('Ignored:', err); // no shutdown or alert });
Correct approach:process.on('unhandledRejection', err => { console.error('Critical error:', err); // perform cleanup process.exit(1); });
Root cause:Believing that logging alone is enough without stopping or fixing the app state.
#3Adding .catch() after a delay and expecting no unhandled rejection event.
Wrong approach:const p = Promise.reject(new Error('fail')); setTimeout(() => { p.catch(err => console.log('Caught late:', err)); }, 1000);
Correct approach:const p = Promise.reject(new Error('fail')); p.catch(err => console.log('Caught early:', err));
Root cause:Not realizing Node.js emits unhandled rejection events before late handlers are added.
Key Takeaways
Unhandled rejection handling is essential to catch promise errors that no code explicitly handles, preventing crashes and unpredictable behavior.
Node.js emits 'unhandledRejection' events for rejected promises without handlers, allowing global error logging and cleanup.
Proper error handling involves catching promise rejections early and using global handlers as a safety net, especially in production.
Ignoring unhandled rejections or mishandling them can cause silent bugs, data corruption, or app crashes.
Node.js is evolving to treat unhandled rejections more strictly, so writing robust error handling is future-proof and critical for reliability.

Practice

(1/5)
1. What is the main purpose of using process.on('unhandledRejection') in a Node.js application?
easy
A. To catch errors from promises that were not handled anywhere else
B. To handle synchronous errors in try-catch blocks
C. To restart the Node.js server automatically
D. To log all successful promise resolutions

Solution

  1. Step 1: Understand what unhandled rejections are

    Unhandled rejections happen when a promise fails but no .catch() or try-catch handles the error.
  2. Step 2: Role of process.on('unhandledRejection')

    This event listener catches those unhandled promise errors so you can log or clean up before the app crashes.
  3. Final Answer:

    To catch errors from promises that were not handled anywhere else -> Option A
  4. Quick Check:

    Unhandled promise errors = process.on('unhandledRejection') [OK]
Hint: Unhandled promise errors are caught by process.on('unhandledRejection') [OK]
Common Mistakes:
  • Confusing unhandledRejection with synchronous try-catch
  • Thinking it restarts the server automatically
  • Assuming it logs successful promises
2. Which of the following is the correct syntax to listen for unhandled promise rejections in Node.js?
easy
A. process.on('unhandledRejection', handlerFunction)
B. process.catch('unhandledRejection', handlerFunction)
C. process.listen('unhandledRejection', handlerFunction)
D. process.handle('unhandledRejection', handlerFunction)

Solution

  1. Step 1: Recall Node.js event listening syntax

    Node.js uses process.on(eventName, callback) to listen to events.
  2. Step 2: Match the event name and method

    The event for unhandled promise rejections is 'unhandledRejection', so the correct syntax is process.on('unhandledRejection', handlerFunction).
  3. Final Answer:

    process.on('unhandledRejection', handlerFunction) -> Option A
  4. Quick Check:

    Event listening in Node.js = process.on() [OK]
Hint: Use process.on('unhandledRejection', handler) to catch promise errors [OK]
Common Mistakes:
  • Using process.catch instead of process.on
  • Using process.listen or process.handle which don't exist
  • Mixing event name spelling
3. Consider this Node.js code snippet:
process.on('unhandledRejection', (reason) => {
  console.log('Error:', reason.message);
});

Promise.reject(new Error('Failed promise'));

What will be printed to the console?
medium
A. No output, program crashes silently
B. Error: Failed promise
C. Error: undefined
D. Unhandled promise rejection

Solution

  1. Step 1: Understand the unhandledRejection event handler

    The handler logs the error message from the rejection reason.
  2. Step 2: Analyze the rejected promise

    The promise rejects with new Error('Failed promise'), so reason.message is 'Failed promise'.
  3. Final Answer:

    Error: Failed promise -> Option B
  4. Quick Check:

    Rejected error message logged = 'Error: Failed promise' [OK]
Hint: Unhandled rejection logs error.message from rejected Error object [OK]
Common Mistakes:
  • Expecting no output or silent crash
  • Confusing reason.message with undefined
  • Thinking the event logs generic text
4. You wrote this code to catch unhandled promise rejections:
process.on('unhandledRejection', (error) => {
  console.log('Caught:', error);
});

Promise.reject('Oops!');

But the console shows Caught: Oops! instead of an error message. What is the issue?
medium
A. The handler function must be async to catch rejections
B. The event name should be 'unhandledRejections' (plural)
C. You must use try-catch instead of process.on for promises
D. The rejection reason is a string, not an Error object, so error.message is undefined

Solution

  1. Step 1: Check the rejection reason type

    The promise rejects with a string 'Oops!', not an Error object.
  2. Step 2: Understand how the handler logs the error

    The handler logs the whole error variable, which is the string 'Oops!', so it prints exactly that.
  3. Final Answer:

    The rejection reason is a string, not an Error object, so error.message is undefined -> Option D
  4. Quick Check:

    Rejection reason type affects error.message presence [OK]
Hint: Rejection reason can be any type; strings have no .message [OK]
Common Mistakes:
  • Using wrong event name 'unhandledRejections'
  • Thinking async handler is required
  • Believing try-catch catches unhandled rejections
5. You want to log unhandled promise rejections and then gracefully shut down your Node.js server. Which code snippet correctly implements this behavior?
hard
A. process.on('unhandledRejection', (reason) => { console.error('Unhandled rejection:', reason); process.exit(1); });
B. process.on('unhandledRejection', (reason) => { console.log('Handled rejection:', reason.message); });
C. process.on('unhandledRejection', (reason) => { console.error('Unhandled rejection:', reason); server.close(() => process.exit(1)); });
D. process.on('unhandledRejection', async (reason) => { await server.close(); console.log('Server closed'); });

Solution

  1. Step 1: Understand graceful shutdown

    Graceful shutdown means closing server connections before exiting the process.
  2. Step 2: Analyze each option's shutdown approach

    process.on('unhandledRejection', (reason) => { console.error('Unhandled rejection:', reason); server.close(() => process.exit(1)); }); logs the error, then calls server.close() with a callback to exit after closing. This is correct.
  3. Step 3: Identify why others are incorrect

    process.on('unhandledRejection', (reason) => { console.error('Unhandled rejection:', reason); process.exit(1); }); exits immediately without closing server; C only logs without shutdown; D awaits server.close but does not exit process.
  4. Final Answer:

    process.on('unhandledRejection', (reason) => { console.error('Unhandled rejection:', reason); server.close(() => process.exit(1)); }); -> Option C
  5. Quick Check:

    Graceful shutdown = server.close() then exit [OK]
Hint: Close server before exit to shutdown gracefully [OK]
Common Mistakes:
  • Exiting process immediately without closing server
  • Not calling process.exit after closing server
  • Logging without shutting down server