728x90

본 강의는 자바스크립트의 기초를 대충 안다고 가정하고 시작하는 조금 심도 깊은 포스팅이다.

완전 처음부터 배우고 싶다면 다른 블로그나 책의 글을 참조하기를 바란다.

특별한 추가 설명이 없다면 nodejs가 아닌 브라우저에서 사용하는 js를 의미한다.

 

참고:

javascript docs

ecmascript specification

 

이 문법은 ES6에 추가된 문법중에서 제일 쓰는 사람만 쓰는 문법일 것이다.

물론 쓰는 사람만 쓴다고 안좋다는게 아니라 꽤 이질적인 문법이기 때문이다.

이 개념이 다른언어들에도 존재하는데 변태 자바스크립트는 역시 이 개념을 추가하게 되었다.

 

이번에 소개시켜줄 개념은 바로 Generator이다.

이 문법의 특징은 함수가 특정 지점에서 끝나고 다음 실행 때는 끝난 시점에서 다시 시작된다는 것이다.

백문이 불여 일견이므로 이 신비한 개념을 바로 보기로 하자.

 

function* call() {
    console.log('first call');
    yield 10;
    console.log('second call');
    yield 20;
    console.log('third call');
    yield 30;
}

let gen = call();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

코드를 보면 신기한 부분들이 있을 것이다.

일단 설명을 하기전에 결과 부터 보고 넘어가자.

 

여러분이 예상한대로 됬는가?

사실 뭔지 잘 모를테니까 예상이 잘 안될 수도 있다.

이제 문법에 대해서 설명하도록 하겠다.

 

function* <함수 명>(<파라메터>){
    syntax;
}

위 처럼 *를 붙히는 함수는 generator가 된다.

이 함수는 특별한 함수인데 일반적인 함수와는 다르게 내부에서 yield라는 문법이 사용가능하다.

 

yield - return처럼 함수를 종료한다. 다만 함수를 재 호출(next())할경우 해당 지점에서 시작한다.

 

물론 여러분은 여기서 재 호출이라는 점이 뭔지 알아야한다.

let gen = call();
console.log(gen.next());

여러분이 generator의 함수를 호출할 경우 이는 함수의 호출이라고 생각하기 보단

iterator를 지정한다는 개념으로 생각하면된다.

말이 좀 어려운데 쉽게 말해서 함수를 바로 실행하는게 아니라 함수를 시행하는 standby상태가 된다고 생각해야한다.

즉 위의 let gen = call();이라는 구문은 아직 함수를 실행한게 아니라 함수를 사용가능한 준비상태가 되는 것이다.

 

next() - generator를 실행한다. yield구문까지 실행하고 종료한다. 또한 재호출시 마지막 yield지점에서 시작한다.

 

그 다음 중요한건 next()를 사용해야 함수를 실행하게된다는 것이다.

 

위의 구문을 해석하자면 gen.next를 호출해서 함수를 실행한다. 첫 호출이므로 first call이 출력된다.

그 다음 yield 10에서 종료, 그리고 이 10을 반환값으로 반환한다. 그래서 console.log로 찍으므로 10이 나온다.

여기서 generator는 값을 그냥 던지는게 아니라 object로 감싸서 던진다.

value는 return된 값이고 done은 이 generator가 마지막 yield구문까지 실행했느냐를 의미한다.

마지막 yield구문을 실행했다면 done은 true가 된다.

 

이 generator는 for구문으로 호출할 수도 있다.

이런식으로 예제를 바꿔보자.

 

function* call() {
    console.log('first call');
    yield 10;
    console.log('second call');
    yield 20;
    console.log('third call');
    yield 30;
}

for(let atom of call()){
    console.log(atom);
}

배열을 반복할 때 여러분이 for of를 사용하지 않는가?

이러한 배열을 iterate하다고 하는데 generater역시 반복가능하다.

그래서 for of구문으로 호출할 수 있다.

 

이번에는 done이 안나오는데 for of로 호출할 경우 바로 next().value의 효과가 나게된다.

어짜피 done이 별 의미 없기도하고.

 

그리고 또 이상한 문법이 있다...

generator는 정말 이상한 문법이있는데 바로 yield*문법이다.

 

yield* - generator함수에서 iterator(generator가 활성화된 상태)를 반환할 때사용한다. 

 

말로하면 좀 어려울테니 예제로 보자.

 

function* func1() {
    yield 42;
}

function* func2() {
    yield* func1();
}

const iterator = func2();

console.log(iterator.next().value);

 

func1은 generator인데 func2역시 generator이다.

이 때 func2의 반환 값에 잘보면 yield*라고 되있다.

이 때 여러분이 가능한 선택지는 아래 3가지가 있다.

 

1-yield func1()

2-yield func1

3-yield* func1()

 

세가지 모두 의미가 다르다.

1번의 경우 func2의 반환결과가 이터레이터(반복자)가 되지만 반환된 녀석도 반복자이다.

2번의 경우 func2의 반환결과가 이터레이터가 아니라 제네레이터함수이므로 반복자를 사용하기 위해서는 함수 호출을 해야한다.

3번의 경우에는 func2의 반환결과가 반복자인건 1번과 같으나 자기 자신이 func1 반복자가 되는 것이다.

이렇게 반복자가 반복자를 호출하는경우는 yield*문법을 사용하는게 좋다.

 

그리고 마지막으로 정말 끔찍한 문법이 존재한다.

이거는... 획기적이고 좋긴한데 발상한게 너무 변태 같다.

 

function* foo() {
    console.log(yield);
    console.log(yield);
    console.log(yield);
}
let g = foo();
g.next();
g.next(1);
g.next(2);
g.next(3);

이문법은 뭔가 되게 이상하다.

yield로 반환하는게 없다.

물론 충분히 가능한 일이다. return도 반드시 값을 주는것 아니지 않는가?

근데 이 문법은 정말 신기한게 함수로 반대로 값을 던질 수 있다는 것이다.

 

여기서 첫번 째 next때는 값을 전달할 수 없다.

당연하지만 첫번째 호출 때는 yield가 아니기 때문이다.

하지만 두번째 호출 때는 마지막에서 실행한 yield시점에서 시작하는데 이 때 next에 값을 실을 수 있다.

그러면 놀랍게도 값이 전달된다...

 

정말 신기한 문법인데 이를 이용해서 비동기 콜에서 함수끼리 통신하게 만들 수도 있다.

+ Recent posts