시스템아 미안해
Chapter 11. Asynchronous Programming 본문
자바스크립트는 기본적으로 단일 스레드(single-thread)로 동작한다.
즉, 한 번에 하나의 작업만 처리할 수 있다.
그렇다면 네트워크 요청이나 파일 읽기처럼 오래 걸리는 작업을 하는 동안
프로그램이 멈추지 않게 하려면 어떻게 해야 할까?
바로 비동기 프로그래밍(Asynchronous Programming) 이다.
1. 동기 vs 비동기
동기(Synchronous) 모델에서는 하나의 작업이 끝나야 다음 작업이 시작된다.
예를 들어:
let a = getDataFromServer(); // 서버 응답이 올 때까지 멈춤
console.log(a);
이 코드는 서버 응답이 오기 전까지 프로그램이 멈춘다.
반면, 비동기(Asynchronous) 모델은 기다리지 않는다.
작업이 완료되면 콜백을 통해 결과를 알려주며, 그동안 다른 코드를 계속 실행할 수 있다.
getDataFromServer(data => {
console.log(data);
});
console.log("다음 코드 실행");
즉, “일단 시켜두고 나중에 결과를 받는다”는 구조다.
2. 콜백 (Callback)
비동기의 가장 기본 형태는 콜백이다.
setTimeout(() => console.log("Tick"), 500);
하지만 콜백이 중첩되면 코드가 점점 복잡해진다.
이른바 콜백 지옥(callback hell).
readFile("a.txt", a => {
readFile("b.txt", b => {
readFile("c.txt", c => {
console.log(a, b, c);
});
});
});
이 문제를 해결하기 위해 Promise가 등장했다.
3. 프로미스 (Promise)
Promise는 “아직 완료되지 않았지만, 언젠가 완료될 값”을 표현한다.
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000);
});
promise.then(result => console.log(result));
// → 완료!
- resolve() → 작업 성공 시 호출
- reject() → 작업 실패 시 호출
- then() → 성공 시 실행할 콜백
- catch() → 실패 시 실행할 콜백
체이닝을 통해 깔끔하게 비동기 흐름을 이어갈 수 있다.
doSomething()
.then(result => doSomethingElse(result))
.then(final => console.log(final))
.catch(error => console.error(error));
4. 실패 처리 (Rejection Handling)
프로미스는 실패도 자연스럽게 다룬다.
new Promise((_, reject) => reject(new Error("Fail")))
.then(() => console.log("성공"))
.catch(err => console.log("실패:", err.message));
체인 중간에 예외가 발생해도 자동으로 catch()로 전달된다.
이 덕분에 try/catch처럼 예외 처리를 단순화할 수 있다.
5. async / await
async 함수는 항상 프로미스를 반환한다.
그리고 await 키워드는 프로미스가 완료될 때까지 기다린다.
async function getData() {
let user = await fetchUser();
let posts = await fetchPosts(user.id);
return posts;
}
동기 코드처럼 읽히지만 실제로는 비동기로 동작한다.
이 문법은 Promise 체인보다 훨씬 직관적이고 에러 핸들링도 간단하다.
try {
let data = await getData();
console.log(data);
} catch (err) {
console.error("에러 발생:", err);
}
6. 이벤트 루프 (Event Loop)
자바스크립트는 단일 스레드지만,
이벤트 루프 덕분에 여러 작업을 동시에 진행하는 것처럼 보인다.
이벤트 루프는 다음 순서로 동작한다:
- 실행할 코드(함수)가 호출 스택에 올라간다.
- 비동기 함수(setTimeout, fetch 등)는 큐에 등록된다.
- 현재 실행이 끝나면 큐의 작업이 하나씩 스택으로 올라가 실행된다.
즉, 콜백은 “나중에 실행될 함수 목록”에 들어가 있다가
스택이 비워질 때 실행된다.
7. 비동기 버그 (Asynchronous Bugs)
비동기 코드에서는 여러 작업이 동시에 진행되기 때문에
예상치 못한 순서 문제가 생기기 쉽다.
예를 들어, 다음 코드는 의도대로 작동하지 않는다.
async function chicks(nest, year) {
let list = "";
await Promise.all(network(nest).map(async name => {
list += `${name}: ${await anyStorage(nest, name, `chicks in ${year}`)}\n`;
}));
return list;
}
list += 구문이 각 비동기 호출마다 동시에 실행되어
결과가 덮어씌워질 수 있다.
올바른 방법은 데이터를 배열에 모은 뒤 한 번에 합치는 것이다.
async function chicks(nest, year) {
let lines = network(nest).map(async name => {
return `${name}: ${await anyStorage(nest, name, `chicks in ${year}`)}`;
});
return (await Promise.all(lines)).join("\n");
}
8. 핵심 정리
| Callback | 비동기 작업 완료 후 호출되는 함수 |
| Promise | 미래의 값을 표현하는 객체 (then, catch로 제어) |
| async/await | Promise 기반 코드를 동기식처럼 작성 가능 |
| Event Loop | 비동기 코드 실행 순서를 관리하는 자바스크립트의 핵심 메커니즘 |
| 에러 처리 | try/catch 또는 .catch()로 처리 가능 |
| 주의점 | await 사용 시 코드의 “틈(gap)”을 주의해야 함 |
9. 실무 요약
- 짧고 간단한 비동기 처리 → Promise
- 복잡한 흐름 제어 → async/await
- 동시 처리 → Promise.all, Promise.race
- 에러 추적은 항상 try/catch 또는 .catch()로
현대 자바스크립트에서는 콜백보다
프로미스와 async/await 중심으로 작성하는 것이 표준이다.
읽기 쉽고 유지보수가 훨씬 수월하기 때문이다.
'책 > Eloquent Javascript' 카테고리의 다른 글
| Chapter 10. Modules (0) | 2025.10.23 |
|---|---|
| Chapter 9. Regular Expressions (0) | 2025.10.21 |
| Chapter 8. Bugs and Errors (0) | 2025.10.20 |
| Chapter 6. The secret life of objects (0) | 2025.10.01 |
| Chapter 5. 고차 함수(Higher-Order Functions) (0) | 2025.09.30 |