본문으로 건너뛰기
김신건의 로그

[Javascript] Iterator / Generator

· 수정 · 📖 약 1분 · 236자/단어 #javascript #iterator #generator #protocol
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, TypedArray
  • arguments (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…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기