Compile Ready
Module 9 · Promises

Promise Error Handling

Intermediate14m read10m practice24m total
PromisesErrorscatchfinally

Introduction

Promise errors travel through the same chain as values, but on the rejection path. A rejection keeps moving forward until a rejection handler handles it. .catch(fn) is shorthand for .then(undefined, fn), and what that handler returns determines whether the chain recovers or remains failed.

Why This Matters

Production async bugs often come from swallowed errors, catch blocks in the wrong place, or cleanup code that masks the original failure. Interviews test this because it reveals whether you can reason about failure paths, not just happy paths.

Theory

Rejection propagation

When a promise rejects, JavaScript looks for the next rejection handler in the chain. A .then with only a fulfillment handler does not handle the rejection, so the rejection passes through unchanged. This is why a final .catch can observe errors thrown anywhere above it.

.catch(onRejected) is equivalent to .then(undefined, onRejected). It returns a new promise just like .then.

Throwing and returning in handlers

Handler actionNext promise
Return a fallback valueFulfilled with that fallback; the error is swallowed.
Return a promiseAdopts that promise.
Throw a new errorRejected with the new error.
Return Promise.reject(reason)Rejected with that reason.

A catch in the middle can recover and let later steps run. A catch at the end is usually a terminal reporter unless it rethrows.

Error swallowing

A common bug is logging inside .catch but not rethrowing. If the catch handler returns normally, the chain becomes fulfilled, often with undefined. The caller may think the operation succeeded. If you cannot recover, rethrow the error or return a rejected promise.

.finally semantics

.finally(callback) runs after fulfillment or rejection and receives no value or reason. If it returns normally, the original value or reason passes through. If it throws or returns a rejected promise, it replaces the original outcome with that new rejection.

Unhandled rejections

If a rejection has no handler by the end of the current turn, runtimes report an unhandled rejection. Browsers expose unhandledrejection; Node exposes unhandledRejection. Treat global handlers as telemetry, not normal recovery. Attach local catches where you can add context, recover, or report a precise failure.

Placement rule

Place catches at the level that can make a decision: recover with a fallback, add context and rethrow, or convert the failure into user-facing state. Do not catch only to silence warnings.

Visual Diagrams

Rejection propagation
p0 rejected with Error
        |
        v
then with fulfillment handler only -- pass rejection through
        |
        v
then with fulfillment handler only -- still rejected
        |
        v
catch handles or rethrows
        |
        v
next promise is fulfilled if catch returns, rejected if catch throws

A rejection keeps moving until a rejection handler changes it.

Code Examples

Recover vs rethrow

Returning from catch recovers. Throwing from catch keeps the failure path alive.

Loading…

Browser unhandled rejection telemetry

Global handlers are useful for monitoring, but they should not replace local error handling.

Loading…

`finally` preserves the original result unless it fails

Cleanup should usually avoid throwing so it does not hide the original outcome.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1Promise.reject(new Error('fail'))
2 .then(function () {
3 console.log('then');
4 })
5 .catch(function (error) {
6 console.log('catch ' + error.message);
7 return 'ok';
8 })
9 .then(function (value) {
10 console.log(value);
11 });
12
13console.log('sync');

Predict the output #2

javascript
1Promise.resolve()
2 .then(function () {
3 throw new Error('one');
4 })
5 .catch(function (error) {
6 console.log(error.message);
7 throw new Error('two');
8 })
9 .catch(function (error) {
10 console.log(error.message);
11 });

Predict the output #3

javascript
1Promise.reject('bad')
2 .finally(function () {
3 console.log('finally');
4 })
5 .catch(function (reason) {
6 console.log(reason);
7 });
8
9console.log('sync');

Coding Exercises

Retry a promise-returning operation

Medium

Implement retry(fn, attempts). fn returns a value or promise. Call it until it fulfils or until all attempts are used. If every attempt fails, reject with the last error.

Interview Questions

1What is the difference between returning a value from `catch` and throwing inside `catch`?

Returning a value from catch recovers the chain: the promise returned by catch is fulfilled with that value. Throwing inside catch rejects the next promise, so the error continues to the next rejection handler. Logging and returning normally swallows the error.

Asked at:AmazonGoogleMicrosoft

Follow-ups

  • How would you add context and preserve failure?
  • What should global unhandled rejection handlers be used for?
2How does `.finally` behave with fulfilled and rejected promises?

.finally runs for both outcomes and receives no value or reason. If it returns normally or returns a fulfilled promise, the original outcome passes through. If it throws or returns a rejected promise, the chain rejects with that new reason.

Quiz

1. What does `.catch(handler)` mean?

2. A `catch` logs an error and returns normally. What happens next?

Summary

  • Rejections propagate until a rejection handler handles or transforms them.
  • `.catch(fn)` is shorthand for `.then(undefined, fn)` and returns a new promise.
  • Returning from `catch` recovers; throwing or returning a rejected promise keeps the chain rejected.
  • `.finally` is for cleanup and passes the original outcome through unless it fails itself.

Cheat Sheet

Propagation: rejection skips fulfillment-only handlers.

.catch(fn): same as .then(undefined, fn).

Recover: return a value.

Rethrow: throw or return Promise.reject.

finally: cleanup on both paths; original outcome passes through unless finally fails.

Avoid: logging and swallowing errors unintentionally.