Promise.reject()
ES6+Returns a Promise object that is rejected with the given reason.
Syntax
Promise.reject(reason)Parameters
reason any The reason for rejecting the Promise
Return Value
A Promise that is rejected with the given reason
Examples
Promise.reject(new Error('Something went wrong'))
.catch(error => console.log(error.message)); 📌 When to Use
Use Promise.reject() to produce an immediately rejected promise when a function with an asynchronous contract must fail without doing any asynchronous work. The textbook case is argument validation at the top of a promise-returning function: returning Promise.reject(new Error('User ID is required')) keeps the failure flowing through the caller's existing catch() or try/catch path, whereas throwing synchronously from a non-async function would force every caller to handle two different failure styles. It is equally useful in tests and mocks — mockRejectedValue-style stubs are Promise.reject() under the hood — for simulating network failures, permission errors, and rate limits deterministically. You will also see it inside then() handlers to convert a technically successful response into a controlled failure, such as return Promise.reject(new AuthError()) when a 200 response carries an error payload, although a plain throw inside the handler achieves the same result and usually reads better. Inside async functions, prefer throw: it is idiomatic, and the function wraps it into a rejection automatically. Whatever the context, keep one rule absolute: reject with Error instances, because whatever value you pass becomes the catch handler's only forensic evidence when things go wrong in production.
⚠️ Common Mistakes
Creating rejected promises before a handler is attached — storing them in variables, maps, or class fields for later use. The rejection counts as 'unhandled' as soon as the microtask queue drains, firing unhandledrejection in browsers or crashing Node.js, even though you intended to attach a catch later. Attach the handler immediately, or create the rejected promise lazily inside a function.
Rejecting with strings or bare objects: Promise.reject('failed') gives consumers no stack trace, no error name, and nothing for instanceof checks — and error-tracking services group such failures into one useless bucket. Always pass new Error(...) or a domain-specific subclass.
Expecting Promise.reject(somePromise) to unwrap the way resolve() does. It does not — the rejection reason becomes the promise object itself, so catch handlers receive a promise instead of an error, and code that reads error.message logs undefined.
Mixing return Promise.reject(...) into async functions where throw is the idiom. It works, but it reads inconsistently next to surrounding throw statements, dodges some lint rules, and inside a try block it behaves differently than authors expect — the local catch cannot intercept it unless the value is awaited.
Assuming the reason gets wrapped in an Error automatically. Promise.reject(404) delivers the bare number to the handler; error.message and error.stack are undefined and instanceof checks fail downstream. Nothing converts reasons for you — construct the Error yourself at the rejection site.
✅ Best Practices
Reject with Error subclasses that carry context — a meaningful name (ValidationError), relevant fields (statusCode, field), and a cause referencing the original failure. Your catch blocks then become routing logic instead of fragile message-string parsing.
Validate inputs and preconditions before starting any side effects, returning early rejections so no partial work happens: the caller gets a fast, clean failure and there is nothing to roll back.
Reserve rejections for genuinely exceptional outcomes. Expected, frequent conditions — cache misses, empty search results — are better modeled as fulfilled values like null or an empty array; rejection-driven control flow costs stack captures and buries real errors in noise.
In tests, prefer factory functions — () => Promise.reject(new Error('boom')) — over pre-created rejected constants, so each rejection is born together with its handler and cannot trip unhandled-rejection warnings during test setup.
When translating third-party failures, keep the original attached: Promise.reject(new PaymentError('Charge declined', { cause: err })). The ES2022 cause option preserves the complete diagnostic chain for logs while your user-facing message stays clean and stable.
⚡ Performance Notes
Promise.reject() always allocates a fresh rejected promise — there is no fast path like Promise.resolve()'s pass-through, because unwrapping a reason would make it ambiguous. The measurable cost is rarely the promise itself; it is the Error you should be rejecting with, whose construction captures a stack trace (in V8, cost grows with stack depth). That is trivial for genuine failures but adds up when rejections implement routine control flow on hot paths. Engines also run rejection-tracking bookkeeping: every rejected promise without a handler is registered for potential unhandledrejection reporting and deregistered once a handler attaches, which adds slight overhead and, more importantly, host-level callbacks your monitoring may subscribe to. Delivery follows the standard microtask rules — a catch() attached to an already-rejected promise still runs asynchronously, after the current stack unwinds, never inline.
🌍 Real World Example
Input Validation with Early Rejection
Input validation guarding an update endpoint: both precondition failures return immediately rejected promises with specific Error messages, so the network request never starts and no partial state is written anywhere. Callers handle validation failures and HTTP failures through the same single catch(), which is the point — one error channel regardless of where in the pipeline the failure originated. The messages are safe to present to users, and because the reasons are real Error instances, logs capture stack traces and error-tracking software groups the failures correctly by type.
function updateUser(userId, data) {
if (!userId) {
return Promise.reject(new Error('User ID is required'));
}
if (!data.email || !data.email.includes('@')) {
return Promise.reject(new Error('Valid email is required'));
}
return fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify(data)
}).then(res => res.json());
}
updateUser(null, {}).catch(e => console.log(e.message));
// "User ID is required"