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...of | for 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를 사용하면 이 문제가 없다.