How do you handle errors across callbacks, promises, and async/await?

3 minintermediatenodejserror-handlingasynctry-catchpromises

Quick Answer

Callbacks: check the error-first argument. Promises: attach .catch() (and know Promise.all is fail-fast). async/await: wrap awaits in try/catch. The key is that a try/catch only catches awaited/synchronous errors — it won't catch errors from un-awaited promises or from callbacks that reject out-of-band.

Detailed Answer

Answer: Each async style has its own error channel; mixing them incorrectly is where bugs hide.

Callbacks — error-first argument:

fs.readFile('f.txt', (err, data) => {
  if (err) return handle(err);
  use(data);
});

Promises — .catch:

doWork()
  .then(use)
  .catch(handle);   // catches rejections from the whole chain

async/await — try/catch:

try {
  const data = await readFile('f.txt');
  use(data);
} catch (err) {
  handle(err);
}

Critical subtleties:

  • A try/catch around await only catches the awaited promise. If you forget await, the error escapes as an unhandled rejection:
try {
  doAsync(); // ❌ no await — a rejection here is NOT caught
} catch (e) {}
  • Errors thrown inside a callback passed to something else aren't caught by an enclosing try/catch:
try {
  setTimeout(() => { throw new Error('boom'); }, 0); // ❌ escapes — crashes process
} catch (e) {}
  • In Express, synchronous throws in a handler are caught by Express, but rejected promises are not (pre-Express 5) — you must call next(err) or use an async wrapper.

Rule: match the handler to the async mechanism, and always await (or .catch) every promise.