[Javascript] Iterator / Generator
JS Iterator / Generator, JS Iterator, JS Generator, JS Iterator Protocol, iterable protocol, function*
정의
- Iterator protocol :
next()메서드가{ value, done }반환 - Iterable protocol :
[Symbol.iterator]()가 iterator 반환 - Generator :
function*으로 정의, yield 로 값 제공
JS Symbol 의 well-known symbol Symbol.iterator 가 핵심.
Iterator protocol
const iter = {
next() {
return { value: 1, done: false };
}
};
iter.next(); // { value: 1, done: false }
done: true 면 종료.
Iterable protocol
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
if (current <= last) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const x of range) console.log(x); // 1, 2, 3, 4, 5
[...range] // [1, 2, 3, 4, 5]
내장 iterable
Array,String,Map,Set,TypedArrayarguments(function 안)- DOM
NodeList - generator
[...'abc'] // ['a', 'b', 'c']
[...new Set([1, 1, 2])] // [1, 2]
for (const [k, v] of map) { ... }
Generator
function* range(from, to) {
for (let i = from; i <= to; i++) {
yield i;
}
}
const g = range(1, 3);
g.next(); // { value: 1, done: false }
g.next(); // { value: 2, done: false }
g.next(); // { value: 3, done: false }
g.next(); // { value: undefined, done: true }
[...range(1, 5)] // [1, 2, 3, 4, 5]
generator 함수는 호출 시 즉시 실행 안 함, iterator 반환. next() 호출마다 다음 yield 까지 실행.
yield
function* gen() {
yield 1;
yield 2;
yield 3;
return 'done'; // value: 'done', done: true
}
yield*
다른 iterable 위임.
function* inner() { yield 1; yield 2; }
function* outer() {
yield 0;
yield* inner(); // inner 의 값들을 펼침
yield 3;
}
[...outer()] // [0, 1, 2, 3]
next 에 값 전달
function* foo() {
const a = yield 1;
const b = yield a * 2;
yield b * 2;
}
const g = foo();
g.next(); // { value: 1, done: false }
g.next(10); // a = 10, value: 20
g.next(100); // b = 100, value: 200
generator 와 caller 가 양방향 통신.
return / throw
const g = gen();
g.next();
g.return('early'); // 강제 종료, { value: 'early', done: true }
g.throw(new Error('oops')); // generator 안에서 throw
무한 시퀀스
function* naturals() {
let n = 1;
while (true) yield n++;
}
const g = naturals();
g.next().value; // 1
g.next().value; // 2
// ... 무한 가능
// take(n)
function* take(it, n) {
for (const x of it) {
if (n-- <= 0) return;
yield x;
}
}
[...take(naturals(), 5)] // [1, 2, 3, 4, 5]
async generator
async function* fetchPages(baseUrl) {
let page = 1;
while (true) {
const res = await fetch(`${baseUrl}?page=${page}`);
const data = await res.json();
if (data.length === 0) return;
yield data;
page++;
}
}
for await (const items of fetchPages('/api/posts')) {
process(items);
}
페이지네이션, 스트림 처리.
자주 만나는 패턴
Lazy 평가
function* lazy(arr, fn) {
for (const x of arr) yield fn(x);
}
const it = lazy([1, 2, 3, 4, 5], x => x ** 2);
// 메모리에 결과 배열 미리 안 만듦
사용자 정의 iteration
class LinkedList {
*[Symbol.iterator]() {
let node = this.head;
while (node) {
yield node.value;
node = node.next;
}
}
}
state machine
function* trafficLight() {
while (true) {
yield 'red';
yield 'green';
yield 'yellow';
}
}
함정
1. generator 는 1 회 소비
const g = range(1, 3);
[...g] // [1, 2, 3]
[...g] // [] (이미 소비됨)
다시 쓰려면 range(1, 3) 새로 호출.
2. for…in vs for…of
const arr = ['a', 'b', 'c'];
for (const i in arr) console.log(i); // '0', '1', '2' ← 인덱스
for (const v of arr) console.log(v); // 'a', 'b', 'c' ← 값
iterable 순회는 for...of.
3. spread 의 iterable 요구
[...{ a: 1 }] // ❌ TypeError
{ ...[1, 2] } // { 0: 1, 1: 2 }
배열 spread 는 iterable 필요. 객체 spread 는 enumerable own properties.
참고
이 글의 용어 (3개)
- [Javascript] async/awaitjavascript
- 정의 / 는 기반 비동기 코드를 마치 동기 코드처럼 쓸 수 있게 해주는 ES2017 의 문법 설탕. 본질은 Promise 그 자체, 문법만 다르다. 전체 동작 메커니즘은 글 참조…
- [Javascript] Map / Set / WeakMap / WeakSetjavascript
- 정의 ES6 의 4 가지 컬렉션. 객체 / 배열보다 적합한 경우. | 컬렉션 | 키 | 값 | 강참조 | |:---|:---|:---|:---:| | | 모든 값 | 모든 값 |…
- [Javascript] Symboljavascript
- 정의 은 ES6 도입의 7번째 primitive 타입. 유일성 을 보장하는 식별자. 왜 필요한가 객체의 충돌 없는 키 로 사용. private property 흉내, well-k…
💬 댓글