prefer-promise-reject-errors
It’s good practice to only reject Promises with Error objects. Errors automatically store stack traces for debugging.
Rule Type: Suggestion
Fixable: No
Why This Rule Exists
Error objects provide:
- Stack traces: See where rejection occurred
- Standard format: Consistent error handling
- Type checking: Can check if value is Error
- Better debugging: Shows full call stack
Without Error objects, debugging is much harder.
Rule Details
This rule enforces that Promises are only rejected with Error objects.
Examples
Incorrect Code
Promise.reject("something bad happened");
Promise.reject(5);
Promise.reject();
new Promise(function(resolve, reject) {
reject("something bad happened");
});
new Promise(function(resolve, reject) {
reject();
});
Correct Code
Promise.reject(new Error("something bad happened"));
Promise.reject(new TypeError("something bad happened"));
new Promise(function(resolve, reject) {
reject(new Error("something bad happened"));
});
const foo = getUnknownValue();
Promise.reject(foo);
Options
allowEmptyReject
Type: boolean
Default: false
Allow Promise.reject() with no arguments.
{
"rules": {
"prefer-promise-reject-errors": ["error", {
"allowEmptyReject": true
}]
}
}
With allowEmptyReject: true
Promise.reject(); // OK
new Promise(function(resolve, reject) {
reject(); // OK
});
Benefits of Error Objects
Stack Traces
Error objects capture where they were created, making debugging much easier.
// Without Error - no stack trace
Promise.reject("failed")
.catch(err => {
console.log(err); // "failed" - where did this come from?
});
// With Error - full stack trace
Promise.reject(new Error("failed"))
.catch(err => {
console.log(err.stack);
// Error: failed
// at fetchData (app.js:15:20)
// at async handler (app.js:42:10)
});
Type Checking
// Can't check type of string
Promise.reject("error")
.catch(err => {
if (err instanceof Error) { // false!
// Won't run
}
});
// Can check Error type
Promise.reject(new Error("error"))
.catch(err => {
if (err instanceof Error) { // true
console.error(err.message);
}
});
Error Logging Services
// Logging services expect Errors
import * as Sentry from '@sentry/browser';
// Wrong - missing context
Promise.reject("API failed")
.catch(err => Sentry.captureException(err)); // No stack!
// Right - full context
Promise.reject(new Error("API failed"))
.catch(err => Sentry.captureException(err)); // Stack included!
Common Patterns
API Errors
// Wrong
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(res => {
if (!res.ok) {
return Promise.reject(res.statusText); // String!
}
return res.json();
});
}
// Right
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(res => {
if (!res.ok) {
return Promise.reject(
new Error(`API error: ${res.statusText}`)
);
}
return res.json();
});
}
Validation Errors
// Wrong
function validateUser(user) {
if (!user.email) {
return Promise.reject("Email required");
}
return Promise.resolve(user);
}
// Right
function validateUser(user) {
if (!user.email) {
return Promise.reject(new Error("Email required"));
}
return Promise.resolve(user);
}
// Even better - custom error
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.field = field;
}
}
function validateUser(user) {
if (!user.email) {
return Promise.reject(
new ValidationError("Email required", "email")
);
}
return Promise.resolve(user);
}
Timeout Errors
// Wrong
function timeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject("Timeout"), ms)
)
]);
}
// Right
function timeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(
() => reject(new Error("Timeout")),
ms
)
)
]);
}
Custom Error Types
Extending Error
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
class ValidationError extends Error {
constructor(message, errors) {
super(message);
this.name = 'ValidationError';
this.errors = errors;
}
}
// Use them
function fetchData(url) {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new NetworkError(
'Request failed',
res.status
);
}
return res.json();
})
.then(data => {
if (!validate(data)) {
throw new ValidationError(
'Invalid data',
getErrors(data)
);
}
return data;
});
}
// Handle them
fetchData('/api/users')
.catch(err => {
if (err instanceof NetworkError) {
console.log('Network error:', err.statusCode);
} else if (err instanceof ValidationError) {
console.log('Validation errors:', err.errors);
}
});
Known Limitations
This rule uses static analysis and cannot guarantee that you only reject with Errors:
// Rule can't tell if this is an Error
const possiblyError = getValue();
Promise.reject(possiblyError); // No warning
Async Functions
This rule doesn’t check throw in async functions (they become rejections), but no-throw-literal does:
// This rule doesn't check this
async function example() {
throw "error"; // Use no-throw-literal
}
// But does check explicit rejects
async function example() {
return Promise.reject("error"); // Caught by this rule
}
Configuration
// Default
{
"rules": {
"prefer-promise-reject-errors": "error"
}
}
// Allow empty rejects
{
"rules": {
"prefer-promise-reject-errors": ["error", {
"allowEmptyReject": true
}]
}
}
When Not to Use It
Disable if you intentionally reject with non-Error values. However, this makes debugging harder and is generally not recommended.
Further Reading