Javascript의 Generator

이 글은 자바스크립트의 제너레이터 문법에 대해 간략히 소개한다.


정의

JavaScript의 제너레이터는 function* 으로 정의된 제너레이터 함수가 반환한 객체이다. 이 객체는 이터레이터(iterator)이다.

1
2
3
4
5
6
7
8
9
function* foo() {
yield 1;
yield 2;
yield 3;
}
// foo()로 생성된 제너레이터를 순회하며 값을 읽어간다.
for (let i of foo()) {
console.log(i);
}

Generator 객체와 함수(팩토리)

제너레이터 함수를 호출하면 제너레이터 객체를 반환하고 끝난다.

제너레이터 객체의 next(...args)를 통해 제너레이터의 본문을 일정 부분 실행할 수 있다.

  • 이터레이터는 next() 함수로 파라미터를 전달할 수 없다.
  • 제너레이터가 값을 읽을 수 있기 때문에 협력적 멀티 태스킹이 가능하다.

Generator 기반 협력적 멀티 태스킹

협력적 멀티 태스킹은 코루틴에 나오는 개념이다. (추후 정리할 예정이다.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go(function* producer() {
for (let i = 0; i < 10; i++) {
yield write(i);
yield sleep(100); // -- sleep이 가능해진다!
}
});
go(function* consumer() {
let v;
while (
typeof (v = yield read()) !== 'undefined'
) {
console.log('read:', v);
}
});

이 코드의 go와 같은 함수를 제너레이터 실행기라고 한다. co 라이브러리가 훌륭한 제너레이터 실행기를 제공한다.

제너레이터 실행기는 원래 동기적으로 수행되는 제너레이터를 비동기 호출을 수행하게 만든 다음 callback을 통해 다시 제너레이터를 호출하게끔 하여 비동기 코드를 동기 코드처럼 작성할 수 있게 하는 목적의 함수이다.

제너레이터 실행기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 제너레이터 실행기
function grun(g) {
const it = g();
(function iterate(val) {
const x = it.next(val);
if (!x.done) {
if (x.value instanceof Promise) {
x.value
.then(iterate)
.catch((err) => it.throw(err));
} else {
setTimeout(iterate, 0, x.value);
}
}
})();
}

// 제너레이터 실행기를 사용한 모습.
// 꽤 async-await과 같이 가독성이 있다.
function* theFutureIsNow() {
let data;
try {
data = yield Promise.all([
nfcall(fs.readFile, 'a.txt'),
nfcall(fs.readFile, 'b.txt'),
nfcall(fs.readFile, 'c.txt'),
]);
} catch (err) {
console.error(
'Unable to read one or more input files: ' +
err.message,
);
throw err;
}
yield ptimeout(60 * 1000);
try {
yield nfcall(
fs.writeFile,
'd.txt',
data[0] + data[1] + data[2],
);
} catch (err) {
console.error(
'Unable to write output file: ' +
err.message,
);
throw err;
}
}

Generator 직접 만들어보기

자료 중 재밌는 것이 있었다: Babel은 Generator를 어떻게 바꾸나. 지금 나한테는 바로 이해하긴 어렵다. 직접 만들어보면 이해에 큰 도움이 될 듯 하다.

Generator의 단점: Iterable은 가변 인자가 아니다

제너레이터 객체는 위에서 말했듯 Iterable이지만, 이는 가변 인자와는 달라서, Math.min같은 함수를 이용할 때 spead 연산자로 배열로 만들어 넘겨야 하므로, 인자 전달 부분에서 아쉽다고 한다.

1
2
3
4
5
6
7
8
9
10
11
const foo = function* () {
/* ... */
};

// 제너레이터 객체만 넘길 순 없음.
// 굳이 제너레이터 객체를 넘길거면, 받는 함수 입장에서 이터레이터를 써야 할 듯?
// for-of 문으로.
let min = Math.min(foo);

// spread 연산자로 넘겨줘야 함.
let min = Math.min(...foo());

Generator의 콜 스택?

Calling .next() method just pushes that call on the top of the stack. It will then run the code inside the generator function.

difference: it has to remember the state of all local variables, but engines already know how to do that from the implementation of closures. A generator call will restore the state so that it can continue where it left off.

일반적인 콜 스택과 동일하다고 한다. 다만 제너레이터 내에서 제너레이터를 호출하는 경우 복잡하다고 하는데, 거기까지 알 필요는 없을 듯 하다.

출처: Javascript stack model for generators | StackOverFlow

출처: Javascript Generator의 재미 (2016.12)

출처: Learning Javascript, O Reilly

TODO:

부족한 내용 보충하기. 제너레이터가 개념 뿐 아니라 사용이 중요한 개념이어서 정리가 난잡한데 다음주 중으로 정리하려고 한다.

Javascript의 Generator

https://jsqna.com/js-generator/

Author

Seongbin Kim

Posted on

21-01-15

Updated on

21-01-19

Licensed under

댓글