Generator

Chanhee Jang·2023년 4월 6일
0

JS

목록 보기
8/9

자바스크립트는 함수가 실행되면 완료될 때까지 계속 실행되며 도중에 다른 코드가 끼어들지 못한다.

이를 완전-실행(Run-to-Completion)이라고 부른다.

ES6에 나온 제너레이터는 완전-실행 법칙을 따르지 않는다.


문법

var x = 1;

function* foo() {
    x++;
    yield; // 멈추시오!
    console.log("x:", x);
}

function bar() {
    x++;
}

var it = foo();

// foo의 시작점!
it.next();
console.log(x); // 2
bar();
console.log(x); // 3
it.next(); // x: 3

foo() 가 실행되어도 완전-실행되지 않고 yield문에서 멈췄다. (실제로는 실행중이지만 멈춘 상태다.)

첫번째의 it.next()는 x++을 해주고 yield문에서 멈췄다.

두번째의 it.next()는 yield 이후의 console.log("x:", x); 를 실행한다.


특징

  1. 1회 이상 시작/실행을 거듭할 수 있다. (기존의 JS 함수 실행이 가지는 특징인 완전-실행처럼 동작하지 않는다.)
  2. 반드시 끝까지 실행될 필요는 없다.
  3. 제너레이터를 제어하는 이터레이터(iterator) 객체를 만들고, 해당 객체의 next()를 통해 제너레이터의 코드를 실행시킨다.
    1. 기본적으로 next()를 하면 현재 위치에서 다음 yield, 혹은 제너레이터의 끝까지 실행된다.

제너레이터는 특별한 함수다. 그렇지만 함수의 기본 역할인 입력과 출력은 수행한다.

function* foo(x, y) {
    return x * y;
}

var it = foo(6, 7);
var res = it.next();
console.log(res.value); // 42

next()의 결괏값은 *foo()가 반환한 값을 value 프로퍼티에 저장한 객체다.

즉, yield 는 실행 도중 제너레이터한테 값을 가져와, 반환시켜주는 역할이다.


yield/next()를 통한 입출력 주고받기

function* foo(x) {
    var y = x * (yield);
    return y;
}

var it = foo(6);

var r = it.next(2);
console.log(r.value); // undefined

var r2 = it.next(5);
console.log(r2.value); // 30

var r3 = it.next(3);
console.log(r3.value); // undefined

어떻게 동작하는지 살펴보자.

  1. 처음의 it.next()는 var y = x * (yield) 까지만 실행시킨다. 그러고 yield를 리턴한다.
    yield는 아무것도 없으므로 undefined가 리턴되며 이를 그대로 출력시키게 된다.
  2. 두번째 it.next()는 yield 자리에 자기 자신을 대입해 표현식을 완성시키고 return문까지 실행된다.
    이때, it.next()에 5를 집어넣음으로써 x * 5라는 표현식을 완성시킨다.
    만약 아무것도 넣지 않는다면 undefined가 넣어질 것이고, r2.valueNaN 이 출력된다.
  3. 이미 foo()의 마지막까지 실행되었기 때문에 r3.value는 undefined가 출력된다.

한 제너레이터로부터 여러 이터레이터 생성과 실행

같은 제너레이터한테 여러 이터레이터를 만들고, 그 이터레이터끼리 상호작용이 가능하다.

var z = 1;

function* foo() {
    var x = yield 2;
    z++;
    var y = yield x * z;
    console.log(x, y, z);
}

var it1 = foo();
var it2 = foo();

var val1 = it1.next().value; // 2
var val2 = it2.next().value; // 2

val1 = it1.next(val2 * 10).value; // 40, x:20, z:2
val2 = it2.next(val1 * 5).value; // 600, x: 200, z:3

it1.next(val2 / 2); // 300, x: 20, y: 300, z: 3
it2.next(val1 / 4); // 10, x: 200, y: 10, z: 3

next(), return(), throw()

위쪽의 예시에서는 next()만을 사용했다.

사실 이터레이터가 사용할 수 있는 메소드는 return()과 throw()도 있다.

function* foo() {
    yield 1;
    yield 2;
    yield 3;
}

const it = foo();

var r1 = it.next();
console.log(r1); // { value: 1, done: false }
var r2 = it.return("asdf");
console.log(r2); // { value: "asdf", done: true }
var r3 = it.next();
console.log(r3); // { value: undefined, done: true }

return() 은 제너레이터를 완료 상태로 만들 수 있다.

function* foo() {
    while (true) {
        try {
            yield 1;
        } catch (e) {
            console.log("에러입니닷!");
        }
    }
}

const it = foo();

var r1 = it.next();
console.log(r1); // { value: 1, done: false }
var r2 = it.throw(new Error("asdf"));
console.log(r2);
// "Error caught!"
// { value: 1, done: false }

throw() 는 에러를 던질 수 있게 해준다.


정리

  1. 제너레이터는 일반함수와는 다른, 완전-실행하지 않은 함수의 형태이다.
  2. 멈추고, 재개할 수 있는 함수이고 이를 제너레이터라고 부른다.
  3. 제너레이터를 제어하는 이터레이터는 next(), return(), throw()를 통해 제너레이터를 제어할 수 있다.
  4. 1회 이상 시작/실행을 거듭할 수 있다. (기존의 JS 함수 실행이 가지는 특징인 완전-실행처럼 동작하지 않는다.)
  5. 반드시 끝까지 실행될 필요는 없다.
  6. 제너레이터를 제어하는 이터레이터(iterator) 객체를 만들고, 해당 객체의 next()를 통해 제너레이터의 코드를 실행시킨다.
    i. 기본적으로 next()를 하면 현재 위치에서 다음 yield, 혹은 제너레이터의 끝까지 실행된다.

Refer - YDKJS this와 객체 프로토타입, 비동기와 성능

profile
What is to give light must endure burning

0개의 댓글