Q27 of 40 · JavaScript
How should you handle errors in async JavaScript code?
Short answer
Short answer: Use `try/catch` around `await` expressions for async/await code. For Promise chains, attach `.catch()` at the end. In Node.js, handle `unhandledRejection` and `uncaughtException` process events. Always rethrow or log — swallowing errors silently is the most dangerous anti-pattern.
Detail
Async error handling requires explicit patterns — errors do not propagate to the caller's stack like synchronous exceptions.
async/await with try/catch: Wrapping await in try/catch is the cleanest approach. Be specific about what you catch — a broad catch block can mask unrelated errors.
Promise chain .catch(): Attach .catch() at the end of the chain to handle any rejection that propagated through the chain. .catch(fn) is equivalent to .then(undefined, fn).
Granular vs. global error handling:
- Granular: wrap each logical unit; re-throw if you can't handle the error locally
- Global: Node.js
process.on('unhandledRejection')andprocess.on('uncaughtException')as last-resort safety nets — never the primary strategy
In test automation: Test frameworks (Jest, Playwright) already catch and report rejections. But test helpers and custom fixtures should handle their own async errors and clean up resources in finally blocks regardless of outcome.
Error context: When rethrowing, wrap with context: throw new Error(Failed step: ${err.message}) — generic "Error" messages with no context make debugging painful.
// EXAMPLE
// async/await — try/catch
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
// Add context before rethrowing
throw new Error(`fetchUser(${id}) failed: ${err.message}`);
} finally {
// cleanup always runs
console.log("fetch attempt done");
}
}
// Promise chain
fetch("/api/data")
.then(r => r.json())
.then(data => process(data))
.catch(err => {
logger.error("Pipeline failed", err);
throw err; // rethrow — don't swallow
});
// Anti-pattern — swallowed error
async function bad() {
try {
await riskyOp();
} catch {} // ← silent failure, impossible to debug
}