서버 요청처럼 시간이 걸리는 작업을 다루는 법.
비동기 — Promise 와 async/await
JavaScript는 한 번에 하나만 처리하지만, 네트워크 요청 같은 작업은 기다리지 않고 넘어갑니다. 이를 비동기라고 합니다. 이번 레슨에서는 콜백부터 Promise, async/await 까지 비동기를 다루는 도구를 정리합니다.
학습 목표
- 콜백과 이벤트 루프의 개념을 이해한다
- Promise 로 비동기 결과를 다룰 수 있다
- async/await 로 비동기 코드를 동기처럼 작성할 수 있다
- try-catch 로 비동기 에러를 처리할 수 있다
Promise.all·race·allSettled를 구분해 쓸 수 있다
콜백과 이벤트 루프
가장 원초적인 비동기 처리는 콜백 함수입니다. 작업이 끝나면 호출될 함수를 미리 넘겨둡니다.
console.log('1');
setTimeout(() => console.log('2 (1초 뒤)'), 1000);
console.log('3');
// → 1
// → 3
// → 2 (1초 뒤)
JavaScript 는 이벤트 루프를 통해 시간이 걸리는 작업을 큐에 미뤄두고, 본 작업이 끝난 뒤 차례로 실행합니다. 그래서 3 이 2 보다 먼저 찍힙니다.
⚠️ 주의 — 콜백을 중첩하면 "콜백 지옥"에 빠지기 쉽습니다. 이를 해결하려고 나온 것이 Promise 입니다.
Promise
Promise 는 "미래에 완료될 작업의 결과"를 담는 객체입니다. 성공(then)과 실패(catch)를 나눠 처리합니다.
fetch('https://api.example.com/users')
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error('실패:', err));
직접 만들 수도 있습니다.
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
wait(500).then(() => console.log('0.5초 경과'));
| 상태 | 의미 |
|---|---|
| pending | 아직 진행 중 |
| fulfilled | 성공 (resolve 호출됨) |
| rejected | 실패 (reject 호출됨) |
async / await
.then 체인을 동기 코드처럼 읽기 쉽게 바꿔줍니다. async 함수 안에서 await 로 Promise 가 끝날 때까지 기다립니다.
async function loadUsers() {
try {
const res = await fetch('https://api.example.com/users');
const data = await res.json();
console.log(data);
} catch (err) {
console.error('실패:', err);
}
}
loadUsers();
💡 TIP —
await는 반드시async함수 안에서만 쓸 수 있습니다(최상위 모듈 제외).async함수는 항상 Promise 를 반환합니다.
try-catch 로 에러 처리
await 한 작업이 실패하면 예외가 발생하므로 try-catch 로 감쌉니다.
async function getProfile(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error('프로필 로드 실패:', err.message);
return null; // 폴백 값
}
}
여러 요청을 동시에 — Promise.all
const [users, posts] = await Promise.all([
fetch('/api/users').then((r) => r.json()),
fetch('/api/posts').then((r) => r.json()),
]);
💡 TIP — 순서대로
await하면 느립니다. 서로 독립적인 요청은Promise.all로 병렬 처리하세요.
race 와 allSettled
// race: 가장 먼저 끝나는 하나만 (타임아웃 구현에 유용)
const result = await Promise.race([
fetch('/api/data'),
wait(3000).then(() => { throw new Error('타임아웃'); }),
]);
// allSettled: 모두 끝날 때까지 기다리고, 성공/실패를 각각 보고
const results = await Promise.allSettled([
fetch('/api/a'),
fetch('/api/b'),
]);
results.forEach((r) => {
console.log(r.status); // → 'fulfilled' 또는 'rejected'
});
| 메서드 | 동작 |
|---|---|
all | 모두 성공해야 성공, 하나라도 실패하면 즉시 실패 |
race | 가장 먼저 끝난 결과(성공/실패) 반환 |
allSettled | 모두 끝날 때까지 기다려 각각의 결과 보고 |
any | 가장 먼저 성공한 결과 반환 |
요약
- 비동기 작업은 이벤트 루프 덕분에 본 흐름을 막지 않고 나중에 실행된다
- Promise 는 pending → fulfilled/rejected 상태를 가진다
- async/await 로 비동기 코드를 동기처럼 읽기 좋게 작성한다
- 비동기 에러는
try-catch로 처리한다 - 독립 요청은
Promise.all로 병렬화하고, 부분 실패 허용 시allSettled를 쓴다
연습문제
wait(ms)함수를 만들어 1초 기다린 뒤 "완료"를 출력하는 코드를 작성하세요.- 위
loadUsers를.then체인 버전으로도 작성해 두 방식을 비교하세요. - 두 API 를 병렬로 호출해 결과를 합치는 코드를
Promise.all로 작성하세요.힌트 —
const [a, b] = await Promise.all([...]) Promise.race로 3초 타임아웃을 가진 요청 함수를 구현하세요.힌트 — 실제 요청과 "3초 뒤 reject 하는 Promise"를 경쟁시킵니다.
💡 연습문제 풀이
불러오는 중…
댓글 0
“JavaScript” 강좌에 대한 댓글입니다.