What it does: Catches and handles errors anywhere in a promise chain using .catch() - errors “bubble up” to the nearest error handler.

Error Propagation:

Errors jump to nearest .catch():

javascript

fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/posts/${user.id}`))  // any of these can fail
  .then(response => response.json())
  .then(posts => console.log(posts))
  .catch(error => console.log("Something broke:", error)); // catches ALL errors above

Multiple catches:

javascript

promise
  .then(data => processData(data))
  .catch(error => console.log("Process failed:", error))  // catches processData errors
  .then(result => saveData(result))
  .catch(error => console.log("Save failed:", error));    // catches saveData errors

Implicit try/catch:

Throwing errors automatically rejects:

javascript

new Promise((resolve, reject) => {
  throw new Error("Boom!"); // same as reject(new Error("Boom!"))
}).catch(error => console.log(error));
 
// Also in .then handlers
promise
  .then(data => {
    throw new Error("Processing failed"); // automatically caught
  })
  .catch(error => console.log(error)); // "Processing failed"

Even programming errors get caught:

javascript

promise
  .then(data => {
    someUndefinedFunction(); // ReferenceError
  })
  .catch(error => console.log(error)); // catches the ReferenceError!

Rethrowing Errors:

Handle some, rethrow others:

javascript

promise
  .catch(error => {
    if (error.name === 'ValidationError') {
      console.log("Validation failed, showing user message");
      return "default data"; // recover and continue
    } else {
      throw error; // rethrow - goes to next catch
    }
  })
  .then(data => console.log(data)) // runs if we recovered
  .catch(error => console.log("Unhandled error:", error));

Interview Gotchas:

javascript

// 1. Forgetting .catch at the end
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));
// If fetch fails, error goes unhandled! 💥
 
// 2. .catch continues the chain
promise
  .then(data => { throw new Error("fail"); })
  .catch(error => "recovered")  // returns "recovered"
  .then(data => console.log(data)); // "recovered" - chain continues!
 
// 3. Unhandled rejections
window.addEventListener('unhandledrejection', event => {
  console.log('Unhandled promise rejection:', event.reason);
});
 
new Promise(() => { throw new Error("Oops!"); }); // triggers unhandledrejection

Bottom line: Errors bubble up to the nearest .catch(). Throwing in any handler automatically rejects the promise. Always end chains with .catch() to handle errors gracefully.


The key difference: In regular try/catch, throwing from catch goes UP to the caller. In promises, returning from .catch goes DOWN the chain.

Regular try/catch behavior:

javascript

function doSomething() {
  try {
    throw new Error("initial error");
  } catch (error) {
    return "recovered"; // goes back to caller as normal result
  }
}
 
try {
  let result = doSomething();
  console.log(result); // "recovered" - normal flow continues
} catch (error) {
  console.log("Won't run");
}

Promise .catch() behavior:

javascript

promise
  .then(data => { throw new Error("fail"); })
  .catch(error => {
    // returning = resolving a new promise
    return "recovered"; // creates Promise.resolve("recovered")
  })
  .then(data => console.log(data)); // "recovered" - gets the resolved value

The Mental Model:

Every .then/.catch/.finally returns a NEW promise:

javascript

// What's really happening:
promise
  .then(data => { throw new Error("fail"); })      // returns rejected promise
  .catch(error => "recovered")                     // returns resolved promise with "recovered"  
  .then(data => console.log(data));                // gets "recovered" from resolved promise
 
// To continue error chain, you must THROW:
promise
  .catch(error => {
    if (canRecover) return "fixed";     // ✅ recovery - goes to .then
    else throw error;                   // ❌ still broken - goes to .catch
  })

The Rule:

  • Return from .catch = “I fixed it” → goes to next .then
  • Throw from .catch = “Still broken” → goes to next .catch

This is why promises are more powerful than regular try/catch - you can recover mid-chain and continue normal flow!