C.W.K.
Stream
Lesson 03 of 05 · published

async / await — Promise 위의 sugar (이빨 있는)

~13 min · async, async-await, promises

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"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 함수에 정확한 promise-chain 대응이 있어. 같은 머신, 다르게 입힌 거.
// 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 패턴 줘.

Pippa 의 고백

난 `await` 를 "모던 방식" 이고 promise 를 "옛 방식" 으로 생각했었어. 아빠가 정정. "같은 머신이야. async/await 는 syntactic sugar. `await` 어디든 픽하는 본능이 promise 병렬 시작이 원하는 거일 때 잊게 만들어." 이제 의식적으로 해: 이 줄 직후 값 필요하면 `await`. 나중에 소비할 5 source 로 fan-out 하면 promise 저장하고 소비 지점에서 `Promise.all`. 다른 의도엔 다른 모양.

Code

순차 vs 병렬 — 차이 아는 게 스킬·javascript
// Sequential — only OK when later calls depend on earlier results
async function userBundle(id) {
  const user = await fetchUser(id);          // 100ms
  const posts = await fetchPosts(user.id);    // 100ms — sequential
  const tags = await fetchTags(user.id);      // 100ms — sequential
  return { user, posts, tags };               // total: 300ms
}

// Parallel — start the independent calls together, then await
async function userBundleFast(id) {
  const user = await fetchUser(id);            // 100ms (need this first)
  const [posts, tags] = await Promise.all([
    fetchPosts(user.id),                       // both start now
    fetchTags(user.id),                        // both run together
  ]);
  return { user, posts, tags };                // total: 200ms
}

// Even more parallel when nothing depends on each other
async function siteData() {
  const [home, about, contact] = await Promise.all([
    fetch('/api/home').then(r => r.json()),
    fetch('/api/about').then(r => r.json()),
    fetch('/api/contact').then(r => r.json()),
  ]);
  return { home, about, contact };
}
try/catch 가 ergonomic 한 승리·javascript
// Error handling patterns

// Local handling
async function safeRead(path) {
  try {
    return await readFile(path, 'utf-8');
  } catch (err) {
    if (err.code === 'ENOENT') return null;  // file missing, return null
    throw err;  // anything else, propagate
  }
}

// Inline catch — fallback value
const raw = await readFile(path, 'utf-8').catch(() => '{}');
const data = JSON.parse(raw);

// Don't swallow without intent — this is a bug
async function bad() {
  try {
    await dangerousOp();
  } catch (e) {
    // swallowed — caller has no idea anything failed
  }
  return 'ok';  // lies
}

External links

Exercise

User 데이터 fetch 하는 함수 가져와: getUser(id) 가 user + posts + tags 반환. 세 가지로 짜: (1) 다 순차 await, (2) user await 후 posts/tags 병렬, (3) 셋 다 병렬 (tags 가 user.id 없이 resolve 가능하다고 가정, 예: org-level tags). 각각 시간 재. 그 다음 네 번째: (2) 와 같지만 try/catch 로 tags 실패가 user 데이터 잃지 않게 — tags 실패하면 부분 반환.
Hint
(4) 위해선 병렬 호출만 wrap: try { [posts, tags] = await Promise.all([...]) } catch (e) { tags = []; posts = await fetchPosts(user.id); }. 또는 Promise.allSettled 로 두 결과 다 받고 각 status 검사.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.