본문 바로가기

프로그래밍/JavaScript(JS)

자바스크립트(JavaScript) - 이터레이터(Iterator), 이터러블(Iterable), 제너레이터(Generator)

글에 오류가 있을 수도 있으니 알려주시면 감사하겠습니다.

 

ES6부터 추가된 이터레이터와 제너레이터는 반복적인 처리를 할 때 사용되는 기능입니다. 

이터레이터(Iterator)

이터레이터는 반복 처리가 가능한 객체를 뜻합니다. 이터레이터는 내부적으로 next() 메서드를 가지며 next() 메서드는 IteratorResult객체 value와 done이라는 프로퍼티를 가진 객체를 반환해야 합니다.

// 사용자 정의
function a() {
    var index = 0;
    var data = [1,2,3,4];
    return {
        next() {
            if (index < data.length) {
                return { value:data[index++], done:false };
            }
            else {
                return { value:undefined, done:true };
            }
        }
    }
}

var ab = a();
console.log(ab.next());
console.log(ab.next());
console.log(ab.next());
console.log(ab.next());
console.log(ab.next());

이터러블(Iterable)

이터러블은 반복이 가능한 객체를 뜻합니다. 이터러블은 [Symbol.Iterator] 메서드가 있어야 하며 [Symbol.Iterator]은 이터레이터 객체를 반환해야 합니다. 

// 기본 객체
var d = [1,2,3,4];
for(var v of d) {
    console.log(v);
}

var a = {
    index: 0,
    data: [1, 2, 3, 4],
    [Symbol.iterator]: function () {
        return this;
    },
    next() {
        if (this.index < this.data.length) {
            return { value: this.data[this.index++], done: false };
        }
        else {
            return { value: undefined, done: true };
        }
    }
}

for (var v of a) {
    console.log(v);
}

자바스크립트에서 기본적으로 String, Array, TypedArray, Map, Set 객체들은 내장 Iterable입니다.

 

Iterable인 객체는 for/of 문법으로 반복이 가능합니다.

 

제너레이터(Generator)

제너레이터는 이터레이터를 좀 더 유연하게 사용할 수 있는 기능입니다. 이 제너레이터는 코드를 일시 정지 재시작을 할 수 있습니다. 코드가 실행된 상태가 유지가 되기 때문에 정지된 상태부터 다시 시작할 수 있습니다.

 

제너레이터의 선언은 함수 선언문에 *을 붙여 선언됩니다. 

function* a() {
    //
}

 

제너레이터가 호출되면 이터레이터가 반환되고 이터레이터에서 next() 메서드가 호출되면 정의한 함수의 코드가 실행이 됩니다. 코드가 실행되면서 yield 키워드 까지 실행이 되며 yield를 통해서 값이 반환 됩니다. 다음 이터레이터의 next() 메소드가 호출될 때 다음 yield 키워드까지 실행이 됩니다.

function* a() {
    var index = 0;
    yield index;
}

var ab = a();
console.log(ab.next()); // -> {value: 0, done: false}

더 이상 yield가 없고 실행할 코드도 없다면 IteratorResult의 value는 undefined done은 true로 값이 반환됩니다.

 

만약 아래처럼 yield는 끝났지만 반복문이나 코드가 더 있다면 다음 next() 메서드가 호출될 때 실행이 됩니다.

function* a() {
    var index = 0;
    yield index;
    index++;
    console.log(index);
}

var ab = a();
console.log(ab.next());
console.log(ab.next());

// 결과
// -> {value: 0, done: false}
// -> 1
// -> {value: undefined, done: true}

반복문이 있을 때

function* a() {
    var index = 0;
    yield index;
    while(index < 5)
    {
        index++;
        console.log(index);
    }
}

var ab = a();
console.log(ab.next().value);
console.log(ab.next());

// 결과
// -> 0
// -> 1
// -> 2
// -> 3
// -> 4
// -> 5
// -> {value: undefined, done: true}
// ->

만약 반복문에 yield 가 있을 경우 yield에서 중단이 되고 값을 반환하고 다음 next() 메서드가 호출되면 다시 반복문에 있던 yield 다음부터 코드가 실행됩니다.

 

제너레이터도 이터레이터를 반환하기 때문에 for/of 문을 사용이 가능합니다.

 

제너레이터에서 반환된 값은 value 프로퍼티를 사용해서 값을 바로 가져올 수 있습니다.

 

제너레이터 값 넘기기

제너레이터에서는 next() 메서드에 인수를 쓰면 제너레이터에 값이 넘어갑니다. 이때 값은 yield로 넘어오게 됩니다.

function* a() {
    var index = 2;
    var i = yield index;
    yield i;
}

var ab = a();
console.log(ab.next()); // -> {value: 2, done: false}
console.log(ab.next(5)); // -> {value: 5, done: false}

제너레이터와 yield*

제너레이터 함수에 있는 yield에 *을 붙여주면 반복 가능한 이터러블 객체가 넘어오게 되며 각각의 값이 yield 가 적용이 됩니다.

function* a() {
    var index = 0;
    yield index;
    yield* [1,2,3,4];
}

var ab = a();
console.log(ab.next().value); // -> 0
console.log(ab.next().value); // -> 1
console.log(ab.next().value); // -> 2

그밖에 메서드

next() 메서드 말고도 다른 메서드가 존재합니다. return() 메소드와 throw() 메소드가 있습니다.

 

return() 메서드는 말그대로 제너레이터 함수의 작업을 종료하는것입니다. return() 메소드를 사용하면 다음 next() 메소드 부터 작업이 끝난 상태인 IteratorResult 객체가 반환됩니다.({value: undefined, done: true})

 

thow() 메소드는 제너레이터 함수에 예외를 발생시킵니다.

 

참고

모던 자바스크립트 입문(이소 히로시 지음, 서재원 옮김)

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/yield

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Iterators_and_Generators