안녕 ES6. (4/4) Iterator와 Generator

for .. of 구문

for … in 구문이 객체의 모든 속성을 순회하는것과 달리, for … of는 배열뿐만 아니라 Map, Set등의 컬렉션을 순회할때도 데이터만 순회하여 일반적으로 원하는 결과를 얻어낼 수 있다. 이런, for … of 구문이 작동하는 배경에는 Iterator 프로토콜이 존재한다.

Iterator

이터레이터 프로토콜은 객체내에서 값의 순서를 결정하고, 이것을 순회(Iterable)할 수 있도록 한다. 이것은, [Symbol.iterator()]함수를 구현하는것에 따라 결정된다. 앞서 언급했던 배열이나 Map, Set과 같은 컬렉션 객체들은 이미 Iterable객체이기 때문에 별도의 추가 구현없이 for … of 구문으로 순회가 가능한 것이다.

Iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var table = {
[Symbol.iterator]: function() {
var index: 0,
next: function() {
var keys = Object.keys(this).sort()
return {
value: keys[index], done: index++ >= keys.length
}
}
}
}
for (var key of table) {
console.log(key + ' = ' + table[key]);
}

이처럼, next()는 value와 done을 리턴하고 done이 true이면 순회를 종료한다.

Generator

제너레이터는 일반적인 함수가 한번 실행에 한번의 리턴을 하는것과 달리, 여러번의 리턴을 허용한다. Go의 고루틴과 사용 및 동작이 매우 비슷하다. 아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* list(value) {
for (var item of value) {
yield item;
}
}
for (var value of list([1, 2, 3])) {
console.log(value);
}
var iterator = list([1, 2, 3]);
console.log(typeof iterator.next); // function
console.log(typeof iterator[Symbol.iterator]); // function
console.log(iterator.next().value); // 1
for (var value of iterator) {
console.log(value); // 2, 3
}

제너레이터함수는 함수명에 “*”를 붙여 생성한다. 그리고, 함수내에서 yield문을 사용한곳에서 실행을 멈추고 값을 리턴한다. 예제에서 알수 있듯이 사실 제너레이터는 이터레이터를 기반으로 작동하는 함수이다. 제너레이터를 잘 활용하면 비동기 코드로 인한 콜백문제를 쉽게 리팩토링하고, 코드를 깔끔하게 관리할 수 있다.
이 사실을 염두해두고 앞서 Iterator로 만들었던 예제를 다시 작성해 보겠다.

1
2
3
4
5
6
7
8
9
var table = {
[Symbol.iterator]: function*() {
var keys = Object.keys(this).sort();
for (var item of keys) {
yield item;
}
}
}

and

Iterator와 Generator는 매우 획기적이고 중요한 개념이다. 다음 링크에 상세하게 잘 설명되어 있으니 이 글에서 다루지 않은 많은것들을 참고하기 바란다.

ES6 In Depth: 이터레이터(iterator)와 for-of 루프 구문
ES6 In Depth: 제너레이터(Generator)
ES6 In Depth: 제너레이터 (이어서)

이외에도 ES6의 상세한 기능들이 잘 설명되어 있으니 여기서 다루지 않았던 내용들은 ES6 In Depth series에서 참고할 수 있다.