Question
How to Reject a Promise in async/await with TypeScript
Question
How can you reject a promise returned by an async function?
For example, suppose you originally had code like this:
function foo(id: string): Promise<number> {
return new Promise((resolve, reject) => {
someAsyncPromise()
.then(() => resolve(200))
.catch(() => reject(400));
});
}
Now you want to rewrite it using async/await:
async function foo(id: string): Promise<number> {
try {
await someAsyncPromise();
return 200;
} catch (error) {
return 400;
}
}
The issue is that return 400 inside catch produces a resolved promise with value 400, not a rejected promise.
How do you correctly make an async function return a rejected promise in this case?
Short Answer
By the end of this page, you will understand how async functions wrap return values in resolved promises, how errors become rejected promises, and why throw is the correct way to reject from async/await code. You will also see practical TypeScript examples, common mistakes, and patterns used in real projects.
Concept
An async function always returns a promise.
That leads to two important rules:
return valuebecomesPromise.resolve(value)throw errorbecomesPromise.reject(error)
This is the key idea behind rejecting in async/await syntax.
In promise chains, you often write:
return Promise.reject(error);
But in an async function, the more natural equivalent is:
throw error;
So if you want your async function to fail, you should throw, not return a normal value.
Why this matters
If you accidentally return a fallback value inside catch, callers will see the operation as successful, even though something actually failed. That can hide bugs and make error handling confusing.
For example:
Mental Model
Think of an async function as a machine that automatically wraps your output.
- If you hand it a value with
return, it puts that value in a success box. - If you throw an error, it puts that error in a failure box.
So:
return 200means: "This operation succeeded with value 200."throw 400means: "This operation failed with reason 400."
A catch block does not automatically reject. It only gives you a chance to decide what happens next.
Inside catch, you can:
- recover and return a default value
- transform the error and throw a new one
- rethrow the same error
The important part is that return = success, throw = failure.
Syntax and Examples
The core syntax is simple.
Return a resolved promise from an async function
async function successExample(): Promise<number> {
return 200;
}
This is equivalent to:
function successExample(): Promise<number> {
return Promise.resolve(200);
}
Return a rejected promise from an async function
async function failureExample(): Promise<number> {
throw new Error("Something went wrong");
}
This is equivalent to:
function failureExample(): <> {
.( ());
}
Step by Step Execution
Consider this example:
async function foo(): Promise<number> {
try {
await someAsyncPromise();
return 200;
} catch (error) {
throw new Error("Operation failed");
}
}
If someAsyncPromise() succeeds
foo()is called.- Because
fooisasync, it immediately returns a promise. - Execution pauses at
await someAsyncPromise(). - If that promise resolves, execution continues.
return 200runs.- The promise returned by
foo()resolves with200.
If someAsyncPromise() fails
foo()is called.
Real World Use Cases
Rejecting correctly in async functions is important in many common situations.
API requests
If a request fails, the function should reject so the caller can show an error message:
async function getProfile(): Promise<User> {
const response = await fetch("/api/profile");
if (!response.ok) {
throw new Error("Failed to fetch profile");
}
return response.json();
}
Validation
Reject when input is invalid:
async function saveEmail(email: string): Promise<void> {
if (!email.includes("@")) {
throw new Error("Invalid email address");
}
(email);
}
Real Codebase Usage
In real projects, developers usually do more than just throw inside catch. They use patterns that make error handling clearer and more maintainable.
1. Rethrow the original error
If you just want the failure to continue upward:
async function processOrder(): Promise<void> {
try {
await chargeCustomer();
} catch (error) {
throw error;
}
}
This works, though sometimes the try/catch is unnecessary if you are not adding anything.
2. Throw a more meaningful error
async function processOrder(): Promise<void> {
try {
await chargeCustomer();
} catch (error) {
throw new Error("Payment processing failed");
}
}
This is useful when low-level errors need to be translated into business-level messages.
Common Mistakes
1. Returning an error value instead of throwing
Broken example:
async function foo(): Promise<number> {
try {
await someAsyncPromise();
return 200;
} catch {
return 400;
}
}
Problem:
- This resolves with
400 - It does not reject
Fix:
async function foo(): Promise<number> {
try {
await someAsyncPromise();
return 200;
} catch {
throw new Error("Operation failed");
}
}
2. Catching an error and silently hiding it
Broken example:
Comparisons
| Pattern | Result | Use when |
|---|---|---|
return value in async function | Resolved promise | The operation succeeded |
throw error in async function | Rejected promise | The operation failed |
return Promise.resolve(value) | Resolved promise | Usually outside async functions |
return Promise.reject(error) | Rejected promise | Usually outside async functions |
catch { return fallback; } | Resolved promise with fallback |
Cheat Sheet
async function example() {
return 123; // resolves with 123
}
async function example2() {
throw new Error("Failed"); // rejects
}
Rules
asyncfunctions always return a promisereturn value=> resolved promisethrow error=> rejected promiseawaitpauses until a promise settles- rejected
awaitjumps tocatch
Correct rejection pattern
async function foo(): Promise<number> {
try {
await someAsyncPromise();
return ;
} (error) {
();
}
}
FAQ
How do you reject a promise inside an async function?
Use throw, not return. In an async function, throw turns into a rejected promise.
Is return Promise.reject(...) allowed inside an async function?
Yes, but throw is clearer and more idiomatic in async/await code.
Why does return 400 not reject?
Because async functions treat returned values as successful results. It becomes a resolved promise with value 400.
Should I throw numbers like 400?
You can, but it is better to throw an Error object for better debugging and clearer code.
Do I always need a try/catch with await?
No. If you are not recovering from or transforming the error, you can let it bubble up naturally.
What happens if an awaited promise rejects and there is no catch block?
The function returns a rejected promise automatically.
Mini Project
Description
Build a small TypeScript function that simulates loading user data from a server. The project demonstrates the difference between successful resolution, intentional fallback values, and proper rejection using throw inside an async function.
Goal
Create an async function that returns user data on success and rejects with an error on failure.
Requirements
- Create an async function named
getUserName. - Simulate an async request that may succeed or fail.
- Return a username when the request succeeds.
- Reject with an
Errorwhen the request fails. - Show how to call the function with both
awaitand.then().catch().
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.