[Javascript] async/await
JS Async Await, async/await, async await, async-await, async function, 비동기 함수, JavaScript async/await, JS async/await
정의
async / await 는 Promise 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다.
전체 동작 메커니즘은 비동기와 타이밍, 콜백부터 async/await까지의 발전사 글 참조.
세 가지 규칙
async함수는 항상 Promise 를 반환.return value는Promise.resolve(value)와 같다.await expr은expr이 정착할 때까지 함수 실행을 일시정지. 함수 밖 동기 코드는 계속 진행.- 에러는
try/catch로. Promise 의.catch와 동등.
async function loadProfile(userId) {
try {
const userRes = await fetch(`/users/${userId}`);
const user = await userRes.json();
const postsRes = await fetch(`/posts?u=${user.id}`);
return { user, posts: await postsRes.json() };
} catch (err) {
console.error(err);
throw err;
}
}
await 실행 흐름
sequenceDiagram
participant C as 호출자
participant F as async 함수
participant MQ as Microtask Queue
C->>F: 함수 호출
F->>F: await 이전 동기 코드 실행
F->>MQ: Promise 대기 등록
F-->>C: 일시정지, 제어 반환
Note over C: 동기 코드 계속 실행
MQ->>F: Promise 정착 후 재개
F-->>C: 반환값 Promise 에 담김
IMPORTANT
await 이후 코드는 항상 다음 마이크로태스크 틱에 실행. 동기 코드보다 늦고, setTimeout 보다 빠르다.
await 의 실제 동작
await 는 .then 의 문법 설탕이다.
// async/await
async function f() {
const a = await loadA();
return a + 1;
}
// 동치
function f() {
return loadA().then((a) => a + 1);
}
엔진 내부 동작:
- 오른쪽 표현식을 평가, Promise 가 아니면
Promise.resolve로 감쌈. - 함수 상태를 Lexical Environment 에 저장.
- Promise 정착 시 Microtask Queue 에 “함수 재개” 등록.
- 마이크로태스크 차례가 오면 함수 이어 실행.
Promise 체인과의 비교
두 방식은 동등하지만 async/await 가 가독성이 높습니다.
// Promise 체인
function loadUser(id) {
return fetch(`/users/${id}`)
.then(res => res.json())
.then(user => fetch(`/posts?u=${user.id}`))
.then(res => res.json())
.catch(err => { console.error(err); throw err; });
}
// async/await: 동일한 로직, 읽기 쉬움
async function loadUser(id) {
try {
const user = await fetch(`/users/${id}`).then(r => r.json());
return await fetch(`/posts?u=${user.id}`).then(r => r.json());
} catch (err) {
console.error(err);
throw err;
}
}
체인이 길어질수록, 중간 값을 여러 단계에서 참조해야 할수록 async/await 가 유리합니다.
에러 처리 전략
try/catch
async function fetchData(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (err instanceof TypeError) {
console.error('네트워크 오류:', err.message);
} else {
console.error('서버 오류:', err.message);
}
throw err; // 상위로 전파
}
}
결과와 에러를 함께 반환하는 패턴
// Go-style: [data, error] 튜플 반환
async function safe(promise) {
try {
return [await promise, null];
} catch (err) {
return [null, err];
}
}
const [user, userErr] = await safe(fetchUser(id));
if (userErr) { /* 처리 */ }
에러 무시
// 에러를 무시하고 undefined 반환
const data = await fetchData(url).catch(() => undefined);
함정
순차 vs 병렬
// 순차 (총 2초)
const a = await fetch('/a'); // 1초
const b = await fetch('/b'); // 1초
// 병렬 (총 1초)
const [a, b] = await Promise.all([fetch('/a'), fetch('/b')]);
의존성 없는 작업은 Promise.all 로 묶자.
반복문 안의 await
// 순차 처리 (의도적이라면 OK)
for (const id of ids) {
await process(id);
}
// 병렬 처리
await Promise.all(ids.map(process));
forEach 안의 await 는 동작 안 함
// ❌ forEach 는 async 콜백을 기다리지 않음
ids.forEach(async (id) => await process(id));
console.log('done'); // 아직 process 들이 도는 중에 출력
// ✅ for-of 또는 Promise.all
for (const id of ids) await process(id);
// 또는
await Promise.all(ids.map(process));
Top-level await
ES2022 부터 ES Module 최상위에서 await 가능.
// app.mjs
const config = await fetch('/config.json').then(r => r.json());
// 이후 코드는 config 가 로드된 후 실행
CommonJS 모듈에서는 불가.
실전 예시
재시도 (Retry with backoff)
async function withRetry(fn, { retries = 3, backoff = 300 } = {}) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === retries - 1) throw err;
const delay = backoff * Math.pow(2, attempt);
await new Promise(r => setTimeout(r, delay));
}
}
}
const data = await withRetry(() => fetch('/api/data').then(r => r.json()));
타임아웃
async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}
const result = await withTimeout(fetchData('/slow'), 5000);
AbortController 로 취소 가능한 fetch
async function fetchWithAbort(url) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timeoutId);
}
}
동시 실행 수 제한
async function pLimit(tasks, concurrency) {
const results = [];
const running = [];
for (const task of tasks) {
const p = Promise.resolve().then(task).then(r => {
running.splice(running.indexOf(p), 1);
return r;
});
running.push(p);
results.push(p);
if (running.length >= concurrency) {
await Promise.race(running);
}
}
return Promise.all(results);
}
성능 주의사항
// ❌ 불필요한 await - Promise 를 그냥 반환하면 됨
async function getData() {
return await fetchData(); // 마이크로태스크 1개 추가
}
// ✅ await 불필요
async function getData() {
return fetchData(); // Promise 그대로 반환
}
// ⚠️ 단, try/catch 안에서는 await 필요
async function getData() {
try {
return await fetchData(); // 에러를 catch 하려면 await 필요
} catch (err) {
handle(err);
}
}
참고
이 글의 용어 (6개)
- [Javascript] 이벤트 루프javascript
- 정의 이벤트 루프 (Event Loop) 는 싱글 스레드 JavaScript 엔진이 비동기 작업을 처리하기 위해 돌리는 무한 루프다. 콜 스택이 비어있을 때마다 마이크로태스크 큐…
- [Javascript] Error / try-catchjavascript
- 정의 JS 의 에러 처리. 로 던지고 로 잡는다. 내장 Error 타입과 사용자 정의 Error. throw / try / catch 는 에러 유무와 무관하게 실행. 내장 Err…
- [Javascript] Lexical Environmentjavascript
- 정의 Lexical Environment (어휘적 환경) 는 ECMAScript 명세 (§9.1) 가 정의한 내부 객체. 함수가 호출될 때마다 새로 만들어지며, 이 호출의 변수들…
- [Javascript] Microtask Queuejavascript
- 정의 Microtask Queue (마이크로태스크 큐) 는 이벤트 루프 가 매 틱마다 완전히 비울 때까지 처리하는 우선순위 큐다. 매크로태스크 큐보다 항상 먼저 처리된다. ECM…
- [Javascript] Promisejavascript
- 정의 Promise 는 미래의 어떤 시점에 결정될 값을 나타내는 객체다. ECMAScript 2015 (ES6) 에 도입되어 기반 비동기의 가독성과 에러 처리 문제를 해결했다. …
- [Javascript] Promise.all / race / any / allSettledjavascript
- 정의 의 정적 메서드 4 형제. 여러 Promise 를 동시에 다루는 도구. | 메서드 | fulfilled 조건 | rejected 조건 | 결과 | |:---|:---|:--…
이 개념을 다룬 위키 페이지 (23)
- wikiAsynchronous (비동기)
- wiki[Koa] Context (ctx)
- wiki[Koa] Middleware (Onion Model)
- wiki[Koa] Koa vs Express
- wiki[Framework] Koa.js
- wiki[Frontend] React 19.x: Actions, Activity, RSC, Foundation
- wiki[React] useEffect: 동기화의 본질, dep array, cleanup, useEffectEvent
- wiki고차 함수
- wiki[JavaScript] AbortController
- wiki[Javascript] 콜백
- wiki[Javascript] 콜백 지옥
- wiki[Javascript] Error / try-catch
- wiki[Javascript] 이벤트 루프
- wiki[JavaScript] fetch API: 현대 HTTP 클라이언트
- wiki[Javascript] 함수의 종류
- wiki[Javascript] Iterator / Generator
- wiki[Javascript] Microtask Queue
- wiki[Javascript] Optional Chaining / Nullish Coalescing
- wiki[Javascript] Promise
- wiki[Javascript] Promise.all / race / any / allSettled
- wiki[Node.js] 런타임 개요
- wiki[Python] asyncio: 비동기 프로그래밍
- wikiTypeScript
💬 댓글