This error occurs when a Promise remains in a pending state indefinitely because neither resolve() nor reject() is ever called. The code after await never executes, causing operations to hang or the application to appear frozen.
A Promise in JavaScript has three states: pending, fulfilled, or rejected. When you create a Promise, it starts in the pending state and should eventually transition to either fulfilled (via resolve()) or rejected (via reject()). This error indicates that a Promise has been created but the code inside the Promise executor never calls resolve() or reject(), leaving it perpetually pending. When you await such a Promise, execution halts at that point indefinitely because there's no resolution signal. In Node.js, pending Promises don't keep the process alive by default. If the event loop has no other work to do, Node.js will exit even if Promises are still pending, which can lead to unexpected behavior where your code appears to complete but operations never finish.
Add logging before and after await statements to pinpoint where execution stops:
console.log('Before await');
const result = await myPromise();
console.log('After await'); // This line never executes if Promise hangsCheck your console output to see which await statement is causing the hang.
Ensure your Promise executor calls resolve() or reject():
// BAD: Never calls resolve or reject
const badPromise = new Promise((resolve, reject) => {
const result = someOperation();
// Missing resolve(result) or reject(error)
});
// GOOD: Properly settles the Promise
const goodPromise = new Promise((resolve, reject) => {
try {
const result = someOperation();
resolve(result);
} catch (error) {
reject(error);
}
});Implement a timeout mechanism using Promise.race() to prevent indefinite hanging:
function promiseWithTimeout(promise, timeoutMs = 5000) {
const timeout = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
return Promise.race([promise, timeout]);
}
// Usage
try {
const result = await promiseWithTimeout(myPromise(), 5000);
console.log('Success:', result);
} catch (error) {
console.error('Failed or timed out:', error.message);
}When wrapping callback-based functions in Promises, ensure all code paths call resolve/reject:
// BAD: Only resolves on success, never rejects
const badWrapper = new Promise((resolve) => {
fs.readFile('file.txt', (err, data) => {
if (!err) {
resolve(data);
}
// Missing reject(err) for error case
});
});
// GOOD: Handles both success and error
const goodWrapper = new Promise((resolve, reject) => {
fs.readFile('file.txt', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
// BETTER: Use built-in promisified versions
import { readFile } from 'fs/promises';
const data = await readFile('file.txt');Ensure errors inside Promise executors are caught and rejected:
// BAD: Throws error but doesn't reject
const badPromise = new Promise((resolve, reject) => {
throw new Error('Something failed'); // Not caught!
});
// GOOD: Wrap in try-catch
const goodPromise = new Promise((resolve, reject) => {
try {
const result = riskyOperation();
resolve(result);
} catch (error) {
reject(error);
}
});
// BETTER: Use async/await which auto-wraps errors
const betterPromise = (async () => {
const result = await riskyOperation();
return result;
})();Always add .catch() handlers or try-catch blocks to detect rejections:
// Unhandled rejection (may be silent)
myPromise();
// With error handling
myPromise()
.catch(err => console.error('Promise rejected:', err));
// Or with async/await
try {
await myPromise();
} catch (err) {
console.error('Promise rejected:', err);
}Also add a global handler to catch any unhandled rejections:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});Debugging Pending Promises: Use Chrome DevTools or VS Code debugger with async stack traces enabled to trace Promise chains. The --trace-warnings flag in Node.js can help identify problematic Promises: node --trace-warnings app.js.
Promise.race() Gotcha: In Promise.race(), if all Promises hang, the race itself hangs. Always include at least one timeout Promise in race scenarios.
Event Loop Behavior: Unlike browsers, Node.js will exit if the event loop has no pending timers or I/O operations, even if Promises are pending. This can mask the issue in production. Use tools like why-is-node-running to diagnose unexpected exits.
Testing Considerations: When writing tests, use test framework timeout configurations (Jest's timeout option, Mocha's this.timeout()) to catch hanging Promises early. Consider using libraries like p-timeout for consistent timeout handling across your codebase.
AbortController Pattern: For fetch requests and other abortable operations, use AbortController with timeouts to properly cancel operations rather than leaving them hanging:
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request timed out');
}
throw error;
}Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process