JavaScript Async Debugging Case Study | Tracing Errors in Promise Chains
이 글의 핵심
Debugging async JavaScript: Promise chains, async/await, stack traces, and production monitoring.
Introduction
UnhandledPromiseRejection is one of the most common Node.js warnings. This post walks through finding and fixing errors in real async code.
What you will learn
- Why Promise errors “disappear”
- How to improve async stack traces
- async/await error-handling patterns
- Production monitoring (e.g. Sentry)
Table of contents
- Problem: intermittent unhandled rejections
- Symptom: errors vanish
- Tracing Promise chains
- Root cause: missing error handler
- Fix 1: async/await
- Fix 2: global handlers
- Fix 3: error boundary pattern
- Monitoring: Sentry
- Closing thoughts
1. Problem
Logs
(node:12345) UnhandledPromiseRejectionWarning: Error: Database connection failed
at Database.connect (database.js:45:15)
Traits
- Hard to repro locally
- A few times per day
- Little context on caller
2. Symptom
Buggy route
app.get('/users/:id', (req, res) => {
getUserData(req.params.id)
.then(user => {
res.json(user);
});
// Missing .catch()
});
async function getUserData(id) {
const user = await db.query('SELECT * FROM users WHERE id = ?', id);
if (!user) {
throw new Error('User not found');
}
return user;
}
Why it fails silently at the edge
Rejection propagates; without .catch (or try/catch in an async handler), Node reports UnhandledPromiseRejection.
3. Tracing
{
"scripts": {
"start": "node --trace-warnings --async-stack-traces server.js"
}
}
Improved stacks often show the route file and line.
4. Root cause
Common patterns:
.thenwithout.catchasynchandler without try/catch and no Express error forward.catchthat only logs and swallows errors needed downstream
5. Fix 1: async/await
app.get('/users/:id', async (req, res) => {
try {
const user = await getUserData(req.params.id);
res.json(user);
} catch (err) {
console.error('Error fetching user:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
6. Fix 2: global handlers
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
errorLogger.log({
type: 'unhandledRejection',
reason: reason,
stack: reason.stack,
timestamp: new Date().toISOString(),
});
});
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});
Express wrapper
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await getUserData(req.params.id);
res.json(user);
}));
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({ error: err.message });
});
7. Fix 3: service layer
Map infrastructure errors to domain errors (NotFoundError, ServiceUnavailableError) and handle by type in one error middleware.
8. Monitoring
const Sentry = require('@sentry/node');
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());
app.use(Sentry.Handlers.errorHandler());
9. Patterns
Promise.allSettledvs barePromise.allwhen partial failure is OK- Timeouts with
Promise.race - Retries with exponential backoff
Closing thoughts
- Every async path needs a rejection handler (
.catchor try/catch orasyncHandler) - Global safety net for slips
- Sentry (or similar) for production
- async/await for readability
Assume errors will happen—design the path explicitly.
FAQ
Q1. Promise chains vs async/await?
Prefer async/await with try/catch at boundaries; chains are fine with explicit .catch.
Q2. try/catch everywhere?
Centralize at HTTP layer + domain boundaries; don’t wrap every line.
Q3. Should unhandled rejections kill the process?
Modern Node may exit; use process managers and fix root causes.
Related posts
- JavaScript async programming
- JavaScript Promises
- JavaScript async/await
Keywords
JavaScript, async, Promise, async/await, Unhandled Rejection, error handling, debugging, Sentry, Node.js, case study