JavaScript에서 비동기적으로 값이 도착하는 시퀀스를 순회하기 위한 프로토콜과 구문.

해당 개념이 필요한 이유

  • 네트워크 스트림, SDK 이벤트 등 “값이 언제 올지 모르는” 데이터를 순차 처리해야 할 때 사용
  • 일반 for...of는 동기 데이터만 처리 가능. Promise를 yield하면 Promise 객체 자체가 나옴
  • for await...of는 각 값을 자동으로 await하여 resolved 값을 받음

AS-IS (for…of로 비동기 데이터 처리)

function* generator() {
  yield Promise.resolve("hello");
  yield Promise.resolve("world");
}
 
for (const val of generator()) {
  console.log(val);
}
// 출력: Promise { "hello" }
// 출력: Promise { "world" }    ← Promise 객체가 그대로 나옴

TO-BE (for await…of로 비동기 데이터 처리)

async function* generator() {
  yield "hello";
  yield "world";
}
 
for await (const val of generator()) {
  console.log(val);
}
// 출력: "hello"
// 출력: "world"    ← resolved 값이 나옴

프로토콜 구조

Iterable (동기)AsyncIterable (비동기)
프로토콜 메서드[Symbol.iterator]()[Symbol.asyncIterator]()
next() 반환값{ value, done }Promise<{ value, done }>
소비 구문for...offor await...of

AsyncIterable 구현 방법 2가지

1. async generator 함수 (간편)

async function* fetchPages(urls: string[]) {
  for (const url of urls) {
    const res = await fetch(url);
    yield await res.json();   // 각 yield마다 await 가능
  }
}
 
for await (const page of fetchPages(urls)) {
  console.log(page);
}

2. Symbol.asyncIterator 직접 구현 (수동)

const asyncIterable = {
  [Symbol.asyncIterator]() {
    let i = 0;
    return {
      next() {
        if (i >= 3) return Promise.resolve({ value: undefined, done: true });
        return Promise.resolve({ value: i++, done: false });
      },
      return() {
        // break/return 시 호출 — 리소스 정리용
        return Promise.resolve({ value: undefined, done: true });
      }
    };
  }
};

주의: rejected Promise와 finally

sync generator에서 rejected Promise를 yield하면 finally 블록이 호출되지 않는다:

function* gen() {
  try {
    yield Promise.reject(new Error("fail"));
  } finally {
    console.log("finally");  // ← 호출 안 됨!
  }
}
 
for await (const v of gen()) { /* ... */ }

이유: for await...of가 sync generator를 async로 래핑할 때, rejection이 generator 내부가 아닌 외부에서 발생하기 때문. async generator를 사용하면 이 문제가 없다.

참고 문서