JavaScript 비동기 처리 패턴 정리: Callback부터 Async/Await까지
문제 상황
레거시 코드베이스에 Callback 패턴과 Promise가 섞여있고, ES2017의 Async/Await를 도입하려니 어떤 기준으로 사용해야 할지 논의가 필요했다. 특히 에러 핸들링 방식이 제각각이라 유지보수가 어려웠다.
Callback 패턴
가장 전통적인 방식. Node.js의 Error-first callback 컨벤션을 따른다.
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
중첩이 깊어지면 Callback Hell이 발생한다. 가독성이 떨어지고 에러 처리가 번거롭다.
Promise
ES2015부터 표준화. 체이닝으로 가독성을 개선했다.
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(err => console.error(err));
.catch()로 에러 핸들링이 명확해졌다. 하지만 여전히 체이닝이 길어지면 읽기 어렵다.
Async/Await
ES2017 문법. 동기 코드처럼 작성할 수 있어 가독성이 좋다.
async function loadUserPosts(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return posts;
} catch (err) {
console.error(err);
}
}
try/catch로 에러 처리가 직관적이다. 디버깅도 쉽다.
결론
신규 코드는 Async/Await를 기본으로 사용하기로 했다. Promise는 Promise.all() 같은 유틸리티 메서드 사용 시에만 직접 다루고, Callback은 레거시 라이브러리 대응용으로만 유지한다. 일관된 에러 핸들링 패턴으로 코드 품질이 개선됐다.