자바스크립트의 비동기 처리: Callback 지옥에서 Async/Await 천국까지
JavaScript
자바스크립트는 싱글 스레드(Single Thread) 언어입니다. 즉, 한 번에 하나의 일만 처리할 수 있습니다. 그런데 어떻게 수많은 네트워크 요청과 사용자 입력을 동시에 처리하는 것처럼 보일까요? 그 비결은 바로 **이벤트 루프(Event Loop)**와 **비동기 삼형제(Callback, Promise, Async/Await)**에 있습니다.
1. 태초에 Callback이 있었다
초기에는 비동기 작업이 끝난 후 실행할 함수를 인자로 넘겨주었습니다.
getData(function(a) {
getMoreData(a, function(b) {
getFinalData(b, function(c) {
console.log(c);
});
});
});
이것이 그 유명한 **콜백 지옥(Callback Hell)**입니다. 가독성은 나락으로 가고, 에러 처리는 거의 불가능에 가깝습니다.
2. 구세주 Promise의 등장 (ES6)
2015년, Promise가 등장하며 비동기 처리가 우아해졌습니다. 성공(resolve)과 실패(reject)를 표준화된 객체로 다룰 수 있게 되었고, 체이닝(Chaining)을 통해 깊이(Depth)를 줄였습니다.
getData()
.then(a => getMoreData(a))
.then(b => getFinalData(b))
.then(c => console.log(c))
.catch(err => console.error(err));
3. 완성형 Async/Await (ES8)
Promise도 훌륭하지만, 여전히 .then()이 꼬리를 무는 형태는 동기 코드와 이질감이 있었습니다. Async/Await는 비동기 코드를 마치 동기 코드처럼 작성하게 해줍니다.
async function main() {
try {
const a = await getData();
const b = await getMoreData(a);
const c = await getFinalData(b);
console.log(c);
} catch (err) {
console.error(err);
}
}
가독성이 압도적으로 좋아졌고, try/catch 문을 사용하여 동기/비동기 에러를 동일한 흐름으로 처리할 수 있게 되었습니다.
4. 주의할 점: 병렬 처리
await을 남발하면 병목이 생길 수 있습니다. 서로 의존성이 없는 요청이라면 Promise.all을 사용하여 병렬로 처리해야 성능을 높일 수 있습니다.
// ❌ 나쁜 예 (직렬)
const user = await getUser();
const posts = await getPosts(); // getUser가 끝날 때까지 대기
// ✅ 좋은 예 (병렬)
const [user, posts] = await Promise.all([getUser(), getPosts()]);
비동기는 자바스크립트의 심장입니다. 이를 자유자재로 다루는 것이 중급 개발자로 가는 관문입니다.