This warning occurs when a Promise rejection is initially left unhandled, triggering Node.js to track it, and then a catch handler is attached later in a subsequent event loop tick.
The PromiseRejectionHandledWarning is a somewhat unusual warning that indicates a promise rejection was eventually handled, but not immediately when the rejection occurred. This is essentially the inverse of the more common UnhandledPromiseRejectionWarning. When a promise is rejected but has no error handler attached (no .catch(), no try/catch), Node.js tracks it as potentially unhandled. If you then attach an error handler later—in a subsequent event loop tick—Node.js emits this warning to indicate the rejection was handled asynchronously rather than synchronously. The warning sequence works like this: (1) A promise is rejected without an immediate handler, (2) Node.js marks it as potentially unhandled and tracks it, (3) Later, you attach a .catch() or error handler, (4) Node.js detects the delayed handling and emits the PromiseRejectionHandledWarning. This pattern is problematic because during the time between the rejection and the handler attachment, your application could have crashed or behaved unexpectedly.
Always attach .catch() handlers or use try/catch blocks at the same time you create or invoke promise-returning functions:
// ❌ Bad - Handler attached later
const promise = fetchData();
setTimeout(() => {
promise.catch(err => console.error(err)); // Too late!
}, 100);
// ✅ Good - Handler attached immediately
const promise = fetchData().catch(err => console.error(err));
// ✅ Good - Using async/await with try/catch
async function run() {
try {
await fetchData();
} catch (err) {
console.error(err);
}
}The error handler must be attached before the current call stack clears.
Do not use setTimeout(), setImmediate(), or other async scheduling around promise error handlers:
// ❌ Bad - Asynchronous handler attachment
function processData() {
const promise = riskyOperation();
setImmediate(() => {
promise.catch(handleError);
});
}
// ✅ Good - Immediate handler attachment
function processData() {
riskyOperation().catch(handleError);
}
// ✅ Good - Proper async/await pattern
async function processData() {
try {
await riskyOperation();
} catch (err) {
handleError(err);
}
}If you need to perform cleanup in a finally block, ensure it doesn't delay error handling:
// ❌ Bad - Await in finally can cause async handling warning
async function example() {
try {
await operation();
} finally {
await cleanup(); // This delays the rejection handling
}
}
// ✅ Good - Handle errors before finally
async function example() {
try {
await operation();
} catch (err) {
console.error('Operation failed:', err);
throw err;
} finally {
await cleanup();
}
}As a safety net, add global promise rejection handlers:
// Log unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
// Log rejections that were handled asynchronously
process.on('rejectionHandled', (promise) => {
console.warn('Promise rejection was handled asynchronously:', promise);
});Note: Global handlers should be a last resort, not a replacement for proper error handling.
When building promise chains dynamically, ensure error handlers are part of the chain from the start:
// ❌ Bad - Promise created without handler, handler added later
let promise = doSomething();
// ... other code ...
promise = promise.catch(handleError); // May be too late
// ✅ Good - Error handling as part of the initial chain
let promise = doSomething()
.then(doNext)
.catch(handleError);
// ✅ Good - Each step has inline error handling
const result = await doSomething()
.then(doNext)
.catch(err => {
console.error('Step failed:', err);
return fallbackValue;
});The PromiseRejectionHandledWarning was introduced in Node.js v6.6.0 as part of improved promise debugging. It's designed to help developers identify patterns where error handling is added too late in the execution flow.
Understanding Event Loop Timing: The key to avoiding this warning is understanding that promises are resolved/rejected in microtasks, which execute at the end of the current JavaScript execution context. If you schedule handler attachment in a macrotask (setTimeout, setImmediate), it will execute too late.
Testing Environments: This warning commonly appears in test suites where testing frameworks might attach handlers asynchronously. Libraries like Jest sometimes trigger this warning even with proper error handling due to their internal promise management.
Node.js Version Behavior: In Node.js 15+, unhandled promise rejections cause the process to exit with a non-zero code by default. The PromiseRejectionHandledWarning indicates you avoided this crash, but through suboptimal timing.
Promise Tracking Internals: When a promise rejects without a handler, Node.js marks it with kPromiseRejectWithNoHandler and sets a warned flag. When you later attach a handler, Node.js checks if it was already warned about, and emits PromiseRejectionHandledWarning if so.
Suppressing the Warning: While you can suppress this warning using --no-warnings or filtering specific warning types, it's better to fix the underlying timing issue in your code. The warning exists to help you write more robust error handling.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams