"async/await 는 새 메커니즘 아냐. Promise chain 을 비동기성 안 버리고 동기 코드처럼 보이게 하는 문법이야."
async 가 하는 일
함수의 async 키워드는 정확히 두 가지: 함수가 Promise 반환하게 강제, 그리고 내부에서 await 키워드 허용. 일반 값 반환해도 함수 호출이 그 값으로 즉시 fulfill 하는 promise 돌려줘. 안에서 throw 하면 promise 가 throw 된 에러로 reject.
async function ping() {
return 'hi';
}
ping(); // Promise<'hi'>, not 'hi'
await ping(); // 'hi' — unwrapped
async function fail() {
throw new Error('boom');
}
fail(); // Promise rejected with Error('boom')
await fail(); // throws Error('boom') at the await
await 가 하는 일
await 는 둘러싼 async 함수를 그 promise 가 settle 될 때까지 suspend, 그 다음 결과 unwrap. Promise 가 fulfill 하면 await 표현식이 그 값으로 평가. Reject 하면 await 가 throw — 일반 try/catch 가 async 에러 잡는다는 뜻, promise chain 위의 단일 가장 ergonomic 한 승리야.
결정적: await 는 *함수만* suspend, 런타임은 아냐. async 함수가 promise 기다리며 일시정지 동안 event loop 는 계속 돌고, 다른 요청 처리되고, 다른 promise resolve 돼. async/await 는 blocking 아냐 — *함수 단위의 협력 스케줄링* 이야.
Equivalence
// async/await
async function pipeline(path) {
const raw = await readFile(path, 'utf-8');
const data = JSON.parse(raw);
return transform(data);
}
// Equivalent promise chain
function pipeline(path) {
return readFile(path, 'utf-8')
.then(raw => JSON.parse(raw))
.then(data => transform(data));
}두 모양 다 유창하게 읽어 — 모던 코드가 섞어 써, 특히 여러 promise 를 병렬로 시작하려 할 때 (실제로 소비할 때까지 await 안 함).Top-Level await (ESM 전용)
ESM 모듈 (그리고 REPL) 에선 함수 밖 top level 에서 await 가능:
// server.mjs
import { readFile } from 'node:fs/promises';
const config = JSON.parse(await readFile('./config.json', 'utf-8'));
const port = config.port ?? 3000;
// rest of file uses `port`
Top-level await 가 import 자체를 await 가 settle 될 때까지 block. 강력해 — 모듈이 비동기로 초기화 가능 — 그리고 위험해 — 느린 top-level await 가 너 import 하는 모든 모듈 로딩 stall. 신중히 써; 모듈 export 유효성에 엄격히 필요한 작업 아니면 함수 안 lazy 초기화 선호.
에러 처리 — 세 모양
1. await 둘레 try/catch — control flow 가진 로컬 에러 처리에 최선.
2. 전파 두기 — async 함수가 catch 안 하면 rejection 이 await 한 쪽으로 bubble. 라이브러리 코드에선 보통 원하는 거.
3. Promise.allSettled 또는 .catch fallback — 한 작업 실패해도 계속 가고 싶을 때. await fetchUser(id).catch(() => null) 가 try/catch 의식 없이 value-or-null 패턴 줘.