Node.js 8 Async/Await를 실무에 도입하며
배경
팀 프로젝트가 Node.js 6에서 8 LTS로 업그레이드되면서 async/await를 본격적으로 사용할 수 있게 되었다. 기존 코드베이스는 콜백과 Promise가 혼재되어 있어 가독성이 떨어졌고, 특히 순차적인 비동기 처리가 필요한 부분에서 코드가 복잡했다.
기존 코드의 문제
사용자 인증 후 데이터를 조회하고 로그를 남기는 로직이 Promise chain으로 되어 있었다.
function getUserData(userId) {
return authenticateUser(userId)
.then(user => {
return fetchUserProfile(user.id);
})
.then(profile => {
return enrichProfileData(profile);
})
.then(enrichedData => {
return logActivity(userId, 'profile_view')
.then(() => enrichedData);
})
.catch(err => {
console.error('Error:', err);
throw err;
});
}
Async/Await로 전환
동일한 로직을 async/await로 변경하니 훨씬 직관적이었다.
async function getUserData(userId) {
try {
const user = await authenticateUser(userId);
const profile = await fetchUserProfile(user.id);
const enrichedData = await enrichProfileData(profile);
await logActivity(userId, 'profile_view');
return enrichedData;
} catch (err) {
console.error('Error:', err);
throw err;
}
}
주의할 점
1. try-catch 누락
async 함수 내에서 await를 사용할 때 try-catch를 빼먹으면 unhandled rejection이 발생한다. Express 미들웨어에서 특히 조심해야 한다.
// 위험한 패턴
app.get('/user/:id', async (req, res) => {
const data = await getUserData(req.params.id); // 에러 시 크래시
res.json(data);
});
// 안전한 패턴
app.get('/user/:id', async (req, res, next) => {
try {
const data = await getUserData(req.params.id);
res.json(data);
} catch (err) {
next(err);
}
});
2. 병렬 처리 놓치기
순차적으로 await를 쓰면 불필요하게 느려진다.
// 느림 (6초)
const user = await fetchUser(id); // 3초
const posts = await fetchPosts(id); // 3초
// 빠름 (3초)
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id)
]);
결론
팀 전체가 async/await 패턴으로 전환하기로 했고, ESLint에 관련 룰도 추가했다. 콜백 헬은 이제 옛날 이야기가 될 것 같다. 다만 기존 Promise 기반 코드와의 호환성, 에러 핸들링 일관성은 코드 리뷰 때마다 체크하고 있다.