자바스크립트 async await 사용법: 비동기 코드를 쉽게 작성하는 법
자바스크립트 async await 사용법, 왜 필요할까?
자바스크립트 async await 사용법을 제대로 익히면 서버 요청, 파일 읽기, 타이머처럼 시간이 걸리는 비동기 작업을 마치 동기 코드처럼 위에서 아래로 읽기 쉽게 쓸 수 있습니다. async와 await는 ES2017(ES8)에 도입된 문법으로, 내부적으로는 Promise 위에서 동작합니다. 즉 새로운 마법이 아니라 Promise를 더 깔끔하게 쓰게 해주는 "문법적 설탕(syntactic sugar)"입니다.
먼저 Promise를 짧게 짚고 넘어가기
비동기 작업의 결과는 미래에 도착합니다. Promise는 그 "미래의 값"을 담는 상자이며 대기(pending), 이행(fulfilled), 거부(rejected) 세 가지 상태를 가집니다. 기존에는 .then() 체이닝으로 결과를 꺼냈는데, 중첩이 깊어지면 읽기 어려워집니다.
fetch('/api/user')
.then(res => res.json())
.then(user => fetch('/api/posts/' + user.id))
.then(res => res.json())
.then(posts => console.log(posts))
.catch(err => console.error(err));async await 기본 문법
함수 앞에 async를 붙이면 그 함수는 항상 Promise를 반환합니다. 함수 내부에서 await를 쓰면 Promise가 처리될 때까지 기다렸다가 그 결과값을 꺼내 줍니다.
async function getPosts() {
const res = await fetch('/api/user');
const user = await res.json();
const postRes = await fetch('/api/posts/' + user.id);
const posts = await postRes.json();
return posts; // Promise<posts> 로 감싸져 반환됨
}
getPosts().then(posts => console.log(posts));핵심 규칙 두 가지입니다.
await는async함수 안에서만 쓸 수 있습니다(단, 최신 모듈의 최상위에서는 top-level await 가능).await는 Promise가 이행될 때까지 그 함수의 실행만 잠시 멈춥니다. 전체 프로그램이 멈추는 것이 아니라 다른 작업은 계속 진행됩니다.
화살표 함수에서도 사용 가능
const getUser = async (id) => {
const res = await fetch('/api/user/' + id);
return res.json();
};실전 예제: 에러 처리는 try/catch로
비동기 작업은 실패할 수 있습니다. async/await에서는 동기 코드와 똑같이 try...catch로 에러를 잡습니다. 이것이 .catch()보다 직관적이라는 점이 큰 장점입니다.
async function loadUser(id) {
try {
const res = await fetch('/api/user/' + id);
if (!res.ok) {
throw new Error('HTTP 오류: ' + res.status);
}
const user = await res.json();
return user;
} catch (err) {
console.error('사용자 불러오기 실패:', err.message);
return null; // 기본값으로 복구
}
}병렬 실행: 순차 await의 함정 피하기
서로 의존하지 않는 작업을 각각 await하면 불필요하게 순서대로 기다리게 됩니다. 동시에 시작하려면 Promise.all을 사용하세요.
// 느림: A가 끝나야 B를 시작 (총합 시간)
const a = await fetchA();
const b = await fetchB();
// 빠름: A, B를 동시에 시작 (더 긴 쪽 시간)
const [a2, b2] = await Promise.all([fetchA(), fetchB()]);async await vs Promise.then 비교
| 구분 | async / await | .then() 체이닝 |
|---|---|---|
| 가독성 | 위→아래 동기 코드처럼 읽힘 | 중첩 시 가독성 저하 |
| 에러 처리 | try / catch | .catch() |
| 중간 변수 사용 | 쉬움 (지역 변수로 보관) | 스코프 공유가 번거로움 |
| 반복문과 결합 | for...of 안에서 자연스러움 | 까다로움 |
흔한 실수
- await 빠뜨리기:
const data = res.json();처럼 await를 안 쓰면data에 실제 값이 아니라 Promise 객체가 들어갑니다. - forEach 안에서 await:
arr.forEach(async ...)는 완료를 기다리지 않습니다. 순차 처리는for...of, 병렬은Promise.all(arr.map(...))을 쓰세요. - 불필요한 순차 await: 독립적인 요청은
Promise.all로 병렬화하세요. - 최상위 await 오해: 일반 스크립트나 함수 밖에서 await를 쓰면 문법 오류가 납니다.
자주 묻는 질문
Q1. async 함수는 항상 Promise를 반환하나요?
네. return 1;처럼 일반 값을 반환해도 자동으로 Promise.resolve(1)로 감싸집니다. 그래서 호출한 쪽에서 다시 await하거나 .then()으로 값을 꺼내야 합니다.
Q2. await가 전체 프로그램을 멈추나요?
아니요. await는 해당 async 함수의 실행만 일시 중단하고, 그 사이 자바스크립트 엔진은 다른 코드(이벤트, 다른 콜백 등)를 계속 처리합니다. 브라우저가 멈추지 않는 이유입니다.
Q3. try/catch 없이 에러를 처리하면 어떻게 되나요?
처리하지 않은 거부(rejection)는 "Unhandled Promise Rejection" 경고를 발생시킵니다. try/catch를 쓰거나, 호출부에서 .catch()를 붙여 반드시 에러를 처리하세요.