Consumsuming Promises: then, catch, finally

Med

Promises are consumed using .then(), .catch(), and .finally() methods. .then() handles fulfillment, .catch() handles rejection, and .finally() runs regardless of outcome. These methods return new Promises, enabling chaining. Understanding how to properly consume Promises is essential for clean asynchronous code.

Interactive Visualization

CodeSetup
1const p1 = Promise.resolve(1);
2
3const p2 = p1.then(x => {
4 return x + 1;
5});
6
7p2.then(v => console.log(v));
Promise Chain
P1
FULFILLED
1
Console Output
-
Promise.resolve(1) creates P1, already fulfilled with value 1
1 / 5
Key Insight: Every .then() creates a NEW Promise. The handler's return value becomes that new Promise's fulfillment value.

Key Points

  • .then(onFulfilled, onRejected) handles success and optionally error
  • .catch(onRejected) is shorthand for .then(null, onRejected)
  • .finally(onFinally) runs on both success and failure
  • These methods return new Promises (enabling chaining)
  • Unhandled Promise rejections should always be caught
  • Errors in .then() are caught by subsequent .catch()

Code Examples

Basic then and catch

fetch('/api/user')
  .then(response => {
    return response.json(); // Returns a Promise
  })
  .then(user => {
    console.log('User:', user);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

.then() handles successful Promise resolution. .catch() handles any rejection in the chain.

then with Two Arguments

fetch('/api/user')
  .then(
    response => response.json(),     // onFulfilled
    error => ({ error: error.message }) // onRejected
  )
  .then(data => {
    if (data.error) {
      console.log('Handled error:', data.error);
    } else {
      console.log('User:', data);
    }
  });

// Note: Error here won't be caught by the inline handler!

.then() can take two callbacks: success handler and error handler.

catch vs then(null, handler)

// These are equivalent:

fetch('/api/user')
  .then(response => response.json())
  .then(null, error => console.error(error)); // Error only for previous .then()

fetch('/api/user')
  .then(response => response.json())
  .catch(error => console.error(error)); // Catches ALL previous errors

// catch is usually preferred as it catches all errors in the chain

.catch() catches errors from the entire chain. Inline error handler only catches the previous .then().

finally for Cleanup

let loading = true;

fetch('/api/user')
  .then(response => response.json())
  .then(user => console.log(user))
  .catch(error => console.error(error))
  .finally(() => {
    loading = false; // Runs regardless of success/failure
    console.log('Request complete');
  });

// Use case: Hide loading spinner, close connections

.finally() runs after Promise settles, regardless of outcome. Great for cleanup.

Errors in then are Caught by catch

Promise.resolve("start")
  .then(value => {
    throw new Error("Oops!"); // Error in .then()
  })
  .then(value => {
    console.log("Skipped!"); // This won't run
  })
  .catch(error => {
    console.log("Caught:", error.message); // "Caught: Oops!"
  });

// Errors in any .then() are caught by the next .catch()

If a .then() throws an error, it rejects the returned Promise, caught by the next .catch().

Common Mistakes

  • Forgetting to return in .then() (breaks the chain)
  • Using .then() second argument instead of .catch()
  • Not handling errors at all (unhandled rejection)
  • Forgetting that .finally() does not receive the result

Interview Tips

  • Know the difference between .then() two-arg form and .catch()
  • Understand error propagation in Promise chains
  • Know when to use .finally()
  • Always handle Promise rejections