https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/function*

function* 선언 (끝에 별표가 있는 function keyword) 은 generator function 을 정의하는데이 함수는 Generator 객체를 반환니다.

generator function 은 GeneratorFunction 생성자와 function* expression 을 사용해서 정의할 수 있습니다.

문법

function* name([param[, param[, ... param]]]) {
   statements
}
name
함수명.
param

함수에 전달되는 인수의 이름. 함수는 인수를 255개까지 가질 수 있다.
statements
함수의 본체를 구성하는 구문들.

return
generator 객체

설명

Generator는 빠져나갔다가 중간에 처리를 멈추어 놓고 나중에 다시 돌아올 수 있는 함수입니다. 이때 컨텍스트(변수 값)는 출입 과정에서 저장된 상태로 남아 있습니다.

Generator 함수는 호출되어도 즉시 실행되지 않고, 대신 함수를 위한 Iterator 객체가 반환됩니다. Iterator의 next() 메서드를 호출하면 Generator 함수가 실행되어 yield 문을 만날 때까지 진행하고, 해당 표현식이 명시하는 Iterator로부터의 반환값을 반환합니다. yield* 표현식을 마주칠 경우, 다른 Generator 함수가 위임(delegate)되어 진행됩니다.

이후 next() 메서드가 호출되면 진행이 멈췄던 위치에서부터 재실행합니다. next() 가 반환하는 객체는 yield문이 반환할 값(yielded value)을 나타내는 value 속성과, Generator 함수 안의 모든 yield 문의 실행 여부를 표시하는 boolean 타입의 done 속성을 갖습니다. next() 를 인자값과 함께 호출할 경우, 진행을 멈췄던 위치의 yield 문을  next() 메서드에서 받은 인자값으로 치환하고 그 위치에서 다시 실행하게 됩니다.

generator와 yield는 promise와는 아무런 관련이 없다. yield는 그 자체만으로는 동기처리를 해주는 마법의 도구가 아니다. 결국 동기처리를 위해서는 promise가 필요하며, 중지와 재개라는 성격을 가진 generator-yield는 promise처리를 보기 쉽게 도와주는 기능만 할 뿐이다.

예시

간단한 예제

function* idMaker(){
  var index = 0;
  while(index < 3)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
// ...

yield* 를 사용한 예제

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i){
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

Generator 에 인자값을 넘기는 예제

function* logGenerator() {
  console.log(yield);
  console.log(yield);
  console.log(yield);
}

var gen = logGenerator();

// the first call of next executes from the start of the function
// until the first yield statement
gen.next(); 
gen.next('pretzel'); // pretzel
gen.next('california'); // california
gen.next('mayonnaise'); // mayonnaise

Generator 는 생성자로서 사용될 수 없다

function* f() {}
var obj = new f; // throws "TypeError: f is not a constructor"




Promise와 Yield

const co = require('co');
const gT2 = require(__dirname + '/generatorTest2');

function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}

//동기화를 generator로 구현한 경우
co(function* genTest1() {
var res1 = yield p(1);
var res2 = yield p(res1);
console.log('generator1 then end');
});

//1,2, generator1 then end 가 호출된다.

yield p(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다.
・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로  다음 yield로 넘겨준다.


co모듈과 Yield

//generatorTest1.js
const co = require('co');
const gT2 = require(__dirname + '/generatorTest2');

function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}

//동기화를 generator로 구현한 경우
co(function* genTest1() {
var res1 = yield p(1);
var res2 = yield p(res1);
var res3 = yield gT2.genTest2(res2);
console.log('generator1 then end');
});

//generatorTest2.js
function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}


exports.genTest2 = function* (res0) {
var res1 = yield p(res0);
var res2 = yield p(res1);
console.log('generator2 then end');
}

・co 모듈의 가장 좆되는 점은, 가장 상위의 모듈에서만 co를 사용하면, 상위의 모듈에서 import하는 하위의 모듈에서 까지 yield를 자동 next처리 해준다는 점이다!!
이때 알아야 하는 것은 언제 co가 next를 호출해 주느냐 하는 것인데, co는 yield 이후 함수와 그 함수와 연결된 함수들을 체인처럼 쭉 보고 있다가 promise함수가 끝나면 next()를 자동으로 처리하게 되어 다음 yield로 순번을 넘겨준다.
 위의 예에서보면, generatorTest2.js는 co모듈을 따로 사용하고 있지 않지만, co모듈을 사용하는
generatorTest1.js에서 호출 되고 있기 때문에 next()처리를 자동으로 하게된다.
yield p(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다. 
・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로  다음 yield로 넘겨준다.



Javascript의 Iterator와 Generator

원문
http://www.ociweb.com/resources/publications/sett/javascript-iterators-and-generators

ECMAScript 2015(a.k.a. ES6)에서 새롭고 많은 기능들이 추가되었다. 대부분은 이해하기 쉽지만, Iterator와 Generator를 이해하기 위해선 조금 더 노력이 필요하다.
이 글은 여러분이 Iterator와 Generator를 활용할 수 있도록 가이드(base line)를 제공한다.

(역: 예제코드는 NodeJS-v4.2.1(V8) 지원 문법을 기준으로 변경하였다. 원문은 ES6, Firefox 지원 문법 기준)

이 글은 ES6의 문법인 arrow, class, destructing, for-of, let/const, spread operator등을 사용하였는데, 문법들에 대한 이해가 필요하다면 아래 사이트들을 참고하도록 한다.

  1. https://github.com/lukehoban/es6features
  2. http://java.ociweb.com/mark/
  3. (역: NHNEnt-javascript에 ES6의 문법들을 잘 설명한 글들이 있다.)

Iterator

Iterator는 next메서드를 가지고있는 객체이다. 이 메서드는 순차적으로 원소들을 탐색하며, next메서드의 호출시마다 새로운 객체를 반환한다. 반환되는 객체는 value 와(또는 = and/or) done 프로퍼티를 가지고 있으며, 탐색이 완료될 때 done프로퍼티의 값이 true가 된다. 대부분의 Iterator들은 탐색이 완료될때, value를 생략한다. —> return {done: true};

Iterable은 Symbol.iterator라는 메서드를 가지고있는 객체이다. 이 메서드가 Iterator를 반환한다.

Iterable/Iterator Example

피보나치 수열을 생성하는 코드를 작성하였다. fibonacci변수가 참조하고 있는 객체는 Iterable이다.

const fibonacci = {
    [Symbol.iterator]() {
        let n1 = 0, n2 = 1, value;
        return {
            next() {
                value = n1;
                n1 = n2;
                n2 = value + n2;
                // [value, n1, n2] = [n1, n2, n1 + n2]

                if (value > 100) {
                    return {done: true};
                } else {
                    return {value};
                }
            }
        };
    }
};

for (const n of fibonacci) {
    console.log(n);
}

fibonacci

Iterable Object

아래는 기본적인 Iterable 객체/타입들이다.

  1. Array
  2. Set
  3. Map - 원소들을 key/value 쌍으로 탐색한다.
  4. DOM NodeList - Node객체들을 탐색한다.
  5. primitive string - 각 유니코드별로 탐색한다.

Array(typed array 포함)SetMap의 아래 메서드들은 Iterator를 반환한다.

  1. entries - key,value쌍 [key, value]
  2. keys
  3. values

이 메서드들에서 리턴되는 객체는 Iterable이면서 Iterator이다.
Array에서 key는 인덱스들을 나타내며, Set에서는 key,value가 둘 다 value로 같다.

커스텀 객체는 Symbol.iterator 메서드를 추가함으로써 iterable이 될 수 있다.

function objectEntries(obj) {
    let index = 0,
        keys = Object.getOwnPropertyNames(obj)
                .concat(Object.getOwnPropertySymbols(obj)); // Reflect.ownKeys(obj);

    return {
        [Symbol.iterator]() {
            return this;
        },
        next() {
            if (index === keys.length) {
                return {done: true};
            } else {
                let k = keys[index],
                    v = obj[k];
                index += 1;

                return {value: [k, v]};
            }
        }
    };
}

let obj = {foo: 1, bar: 2, baz: 3};
for (const pair of objectEntries(obj)) { // for (const [k, v] of objectEntries(obj))
    console.log(pair[0], 'is', pair[1]); // console.log(k, 'is', v);
}

iterable

Iterable Consumers

다음 문법들은 Iterable을 사용한다.

for-of loop

for (const value of someIterable) {
    // ...
}

spread Operator

// Iterable의 모든  value들이 arr에 추가된다.
let arr = [firstElem, ...someIterable, lastElem];

// Iterable의 모든 value들이 arguments에 추가된다. 
someFunction(firstArg, ...someIterable, lastArg);

spread

Positional Destructing

// Iterable의 첫 3개 값들이 저장된다.
let [a, b, c] = someIterable;

positional-destructing



Generators

Generator는 Iterable이면서 Iterator인 객체의 특별한 종류이다. 이 객체는 일시정지와 재시작 기능을 여러 반환 포인트들을 통해 사용할 수 있다. 이러한 반환 포인트들은 yield 키워드를 통해 구현할 수 있으며, 오직 generator 함수에서만 사용할 수 있다. next호출시마다 다음 yield의 expression이 반환된다.
yield value를 사용하면 한가지 값을 반환할 수 있고, yield* iterable을 사용하면 해당되는 Iterable의 값들을 순차적으로 반환시킬 수 있다.

Generator의 반복이 끝나는 시점은 3가지 경우인데, generator 함수에서 return 사용, 에러 발생 그리고 마지막으로 함수의 끝부분까지 모두 수행된 이후, 이렇게 3가지 경우이다. 그리고 이때 done 프로퍼티가 true가 될 것이다.

이러한 generator함수는 Generator객체를 반환하며, function*키워드로 정의할 수 있다. 또는 클래스에서 메서드 이름 앞에 *을 붙여 정의할 수도 있다.

A Basic Generator

function* myGenFn() {
    yield 1;
    yield 2;
    return 3;
}

let iterator = myGenFn(),
    capture;

console.log('--------------- do-while loop ---------------');
do {
    capture = iterator.next();
    console.log(capture);
} while (!capture.done);

//또는
console.log('\n--------------- for-of loop ---------------');
for (const n of myGenFn()) {
    console.log(n);
}

generator

return문을 사용하지 않았다면, 마지막 next호출에서 value가 3이 아닌 undefined가 반환되었을 것이다.

Fibonacci Generator

다음은 Generator를 이용한 fibonacci 수열 생성이다.

function* fibonacci() {
    let prev = 0, curr = 1; // [prev, curr] = [0, 1];
    yield prev;
    yield curr;
    while (true) {
        let temp = prev;
        prev = curr;
        curr = temp + curr;
        // [prev, curr] = [curr, prev + curr];
        
        yield curr;
    }
}

for (const n of fibonacci()) {
    if (n > 100) break;
    console.log(n);
}

또한 generator 메서드를 포함하고 있는 객체로 구현할 수 있다.

let fib = {
    *[Symbol.iterator]() {
        let prev = 0, curr = 1; // [prev, curr] = [0, 1];
        yield prev;
        yield curr;
        while (true) {
            let temp = prev;
            prev = curr;
            curr = temp + curr;
            // [prev, curr] = [curr, prev + curr];
            
            yield curr;
        }
    }
};

for (const n of fib) {
  if (n > 100) break;
  console.log(n);
}

Generator Methods

  • next(value)
    이 메서드는 다음 값을 얻는 역할을 하며, Iteraotr의 next메서드와 유사하지만, optional argument를 받는다는 점이 다르다.(첫번째 호출에서는 받지 않고 무시한다.) 이 매개변수는 바로 이전의 yield [expression]의 반환값으로 사용된다. (아래 예시를 참고.)
    generator-next-arguments
    function* fibonacci() {
        var fn1 = 1;
        var fn2 = 1;
        while (true) {  
            var current = fn2;
            fn2 = fn1;
            fn1 = fn1 + current;

            var reset = yield current;
            console.log('----> reset', reset);
            if (reset) {
                fn1 = 1;
                fn2 = 1;
            }
        }
    }

    var sequence = fibonacci();
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 2
    console.log(sequence.next().value);     // 3
    console.log(sequence.next().value);     // 5
    console.log(sequence.next().value);     // 8
    console.log(sequence.next(true).value); // reset = true -> yield 1
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 2
    console.log(sequence.next().value);     // 3
  • return(value)
    이 메서드는 매개변수로 온 값을 value로써 반환하고, Generator를 종료시킨다.
    {value: value, done: true}

  • throw(exception)
    이 메서드는 인자로 받은 에러 발생시키고, Generator를 종료시킨다. generator함수 내부의 catch 구문을 통해 처리할 수도 있다. 또한 내부에서 catch 구문으로 처리한 경우 Generator는 종료되지 않는다.
    generator-throw-error

Summary

Iterator가 훌륭하다면 Generator는 더욱 훌륭하다. for-of loop, spread operator, destructing을 완벽하게 활용하기 위해서 Iterator와 Generator에 대한 이해가 중요하다

+ Recent posts