Typescript | Iterator 이해
포스트
취소

Typescript | Iterator 이해

Iterable, Iterator 타입

짧게 정리를 하면 다음과 같다.

  • IterableSymbol.iterator 필드에 ()=>Iterator을 가진 객체
  • Iteratornext 필드에 ()=>IteratorResult을 가진 객체
  • IteratorResult{ value:any, done:boolean } 형식의 타입

제네릭 타입에 대해서는 후술한다.

Iterable

1
2
3
interface Iterable<T, TReturn = any, TNext = any> {
    [Symbol.iterator](): Iterator<T, TReturn, TNext>;
}

Iterable반복 가능한 객체를 의미한다. [Symbol.iterator] 필드에 Iterator<T, TReturn, TNext> 형식의 함수가 있다면 Iterable을 충족한다.

Iterable 이라면 for of 구문이나 스프레드 연산자 ...를 통해 반복할 수 있다. 배열, 문자열 등이 Iterable하다.

1
2
3
4
5
6
7
8
9
const iterable1 = [1, 2, 3]; // Iterable함 
const iterable2 = 'hello';   // Iterable함 

for (const item of iterable1) {
    console.log(item);
}
for (const item of iterable2) {
    console.log(item);
}

Iterator와 IteratorResult

1
2
3
interface Iterator<T, TReturn = any, TNext = any> {
    next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
}

Iterator{ value : 결과, done: boolean }을 반환하는 함수를 next 필드로 가진 객체를 의미한다.

done : false라면, 계속 반복한다는 의미이며, done : true라면 반복이 끝났다는 의미이다.

done : true 인 경우의 value는 일반적으로 무시된다.

1
2
3
4
5
6
7
8
9
10
11
let index = 0;
const iter:Iterator<number> = {
    next() {
        if (index < 10) {
            return { value: index++, done: false }
        }
        else {
            return { value: undefined, done: true }
        }
    }
}

IterableIterator가 혼동된다면 글 처음의 정리를 확인하면 좋다.

짧게 요약하면, IterableIterator를 만들 수 있어야 한다.

제네릭 타입 T, TReturn, TNext

1
2
3
4
5
6
7
interface Iterable<T, TReturn = any, TNext = any> {
    [Symbol.iterator](): Iterator<T, TReturn, TNext>;
}

interface Iterator<T, TReturn = any, TNext = any> {
    next(...[value]: [] | [TNext]): IteratorResult<T, TReturn>;
}

IterableIterator에는 제네릭 타입 <T, TReturn = any, TNext = any>이 존재한다.

Iterable이 가지는 Iterator의 제네릭 타입을 동일해야 한다.

각 타입의 용도를 정리하면 다음과 같다.

  • T : done: false 일 때의 value 타입.
  • TReturn : done: true 일 때 value 타입
  • TNext : Iterator에서 next() 함수의 인자로 들어가는 값

T, TReturn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let index = 0;
const iter:Iterator<number, string> = {
    next() {
        if (index < 2) {
            return { value: index++, done: false }
        }
        else {
            return { value: 'done!', done: true }
        }
    }
}

console.log(iter.next()) // { value : 0, done : false }
console.log(iter.next()) // { value : 1, done : false }
console.log(iter.next()) // { value : 'done!', done : true }

위 코드에서 iterable을 반복했을 때 number 타입으로 받고, 종료 시 string 타입을 받게 된다.

TReturnfor of... 스프레드 연산자에서 반복 종료 시 리턴 값을 받을 방법이 없으므로 의미가 없다. 직접 Iterator를 가져와 next를 호출하는 경우에만 리턴 값을 받을 수 있다.

TNext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let index = 0;
const iter:Iterator<number, undefined, number> = {
    next(add:number) {
        if (index < 10) {
            return { value: (index++) + add, done: false }
        }
        else {
            return { value: undefined, done: true }
        }
    }
}

console.log(iter.next(1)) // { value : 1, done : false }
console.log(iter.next(2)) // { value : 3, done : false }
console.log(iter.next(3)) // { value : 5, done : false }

TNextTReturn 보다도 사용할 일이 적은데, next에 값을 명시적으로 넣어야 하지만 for of... 스프레드 연산자는 next() 인자로 undefined를 넣게 되므로 위 연산 자체를 할 수 없다.

Generator

1
2
3
4
5
6
7
8
9
10
11
function* gen() {
    yield 1;
    yield 2;
    return 'done';
}

const iter = gen();
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: 'done', done: true }
iter.next(); // { value: undefined, done: true }

function 키워드 뒤에 *를 붙이면 Generator가 되며 내부에서 yield 키워드를 사용할 수 있다.

yield를 만나면 해당 위치에서 실행을 잠시 중단하고, next() 호출 시 그 이후부터 재개된다.

return을 만나면 반복이 종료된다. 일반적인 Iterator에서 { value: 반환값, done: true }을 리턴하는 것과 유사하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function* gen() {
    yield 1;
    yield 2;
    return 'done';
}

const iterable:Iterable<number, string> = gen();
const iterator:Iterator<number, string> = gen();

for (const item of iterable) { }

iterator.next()
iterator.next()

Generator를 호출한 결과는 Iterable이면서 Iterator이다. for of 같은 반복자를 사용할 수도 있고, next()로 직접 반복도 가능하다.

1
2
3
4
5
6
7
class Range {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
}

Generator가 Iterator라는건 Iterable 객체의 Symbol.iterator 필드에 Generator가 들어 갈 수 있다는 의미이기도 하다.

1
2
3
4
5
6
7
8
9
function* range(begin:number, end:number) {
    for (let i = begin; i < end; i++) {
        yield i;
    }
}

for (const item of range(15, 20)) {
    console.log(item); // 15~19 까지 출력
}

Generator에는 인자도 넣을 수 있으며 yield의 위치도 자유롭다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.