This error occurs when a Promise is rejected without passing an error object or reason. Instead of a meaningful error message, Node.js displays "undefined", making debugging extremely difficult.
The "undefined (no error reason provided)" error appears when Promise.reject() is called without any argument, or when a promise rejection handler doesn't provide a proper error value. This creates a rejection with undefined as the reason, which makes it impossible to understand what went wrong. This is particularly problematic because promises are meant to carry error information through rejection chains. When the rejection reason is undefined, developers lose all context about the failure, making debugging nearly impossible. It's a symptom of poor error handling practices where errors are caught but not properly propagated or where reject() is called without providing meaningful information.
Pass a proper Error object when rejecting promises:
// Bad - rejecting with undefined
Promise.reject();
Promise.reject(undefined);
reject(); // In promise executor
// Good - reject with Error object
Promise.reject(new Error('Operation failed'));
reject(new Error('Invalid input provided'));
// Also good - reject with meaningful value
Promise.reject('Database connection timeout');
reject({ code: 'INVALID_INPUT', field: 'email' });Error objects provide stack traces and can carry additional context.
Ensure all error paths in async functions provide error information:
// Bad - implicit undefined return
async function fetchUser(id) {
if (!id) {
throw; // Throws undefined
}
return await db.users.findById(id);
}
// Good - explicit error
async function fetchUser(id) {
if (!id) {
throw new Error('User ID is required');
}
return await db.users.findById(id);
}Ensure catch blocks that re-reject pass the error forward:
// Bad - losing error information
fetch('/api/data')
.catch(err => {
console.error('Fetch failed');
return Promise.reject(); // Rejects with undefined
});
// Good - preserving error
fetch('/api/data')
.catch(err => {
console.error('Fetch failed:', err);
return Promise.reject(err); // Pass error forward
});
// Better - throw to propagate
fetch('/api/data')
.catch(err => {
console.error('Fetch failed:', err);
throw err; // Automatically rejects with err
});Catch undefined rejections to help locate the source:
process.on('unhandledRejection', (reason, promise) => {
if (reason === undefined) {
console.error('⚠️ Promise rejected with undefined!');
console.error('Promise:', promise);
console.error('Stack trace to find source:');
console.error(new Error().stack);
} else {
console.error('Unhandled rejection:', reason);
}
});This helps identify where undefined rejections originate. Run with --trace-warnings for more details.
Check promise executors for incorrect reject calls:
// Bad - reject called without argument
new Promise((resolve, reject) => {
if (someCondition) {
reject(); // Rejects with undefined
}
resolve(result);
});
// Good - reject with error
new Promise((resolve, reject) => {
if (someCondition) {
reject(new Error('Condition not met'));
}
resolve(result);
});
// Better - use try/catch
new Promise((resolve, reject) => {
try {
if (!someCondition) {
throw new Error('Condition not met');
}
resolve(result);
} catch (err) {
reject(err);
}
});Use ESLint rules to catch these issues during development:
// .eslintrc.json
{
"rules": {
"prefer-promise-reject-errors": ["error", {
"allowEmptyReject": false
}],
"@typescript-eslint/no-floating-promises": "error",
"no-throw-literal": "error"
}
}The prefer-promise-reject-errors rule specifically catches Promise.reject() without arguments.
TypeScript Protection: TypeScript's strict mode can help prevent undefined rejections. Configure tsconfig.json with:
{
"compilerOptions": {
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}Error Subclassing: Create custom error classes for better error handling:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
// Usage
if (!isValid) {
throw new ValidationError('Invalid email format', 'email');
}Debugging with Error.captureStackTrace: For custom rejection reasons, capture stack traces:
function rejectWithTrace(reason) {
const error = new Error(reason);
Error.captureStackTrace(error, rejectWithTrace);
return Promise.reject(error);
}Common Library Patterns: Some libraries (especially older ones) may reject with non-Error values. Wrap them:
async function safeLibraryCall() {
try {
return await thirdPartyLibrary.method();
} catch (err) {
// Normalize undefined or non-Error rejections
if (!err || !(err instanceof Error)) {
throw new Error(`Library error: ${err || 'undefined'}`);
}
throw err;
}
}Promise.all() and undefined: When using Promise.all(), one undefined rejection stops all:
// If any promise rejects with undefined, hard to tell which one
await Promise.all([
operation1(),
operation2(),
operation3()
]);
// Better - add context to each
await Promise.all([
operation1().catch(err => {
throw new Error(`Operation 1 failed: ${err}`);
}),
operation2().catch(err => {
throw new Error(`Operation 2 failed: ${err}`);
}),
operation3().catch(err => {
throw new Error(`Operation 3 failed: ${err}`);
})
]);Performance Note: Creating Error objects is relatively expensive due to stack trace generation. For high-frequency error paths in hot code, consider using plain objects with error information, but always include enough context to debug the issue.
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