이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.
ES6에서 도입되었으며, 코드 블록을 일시 중지했다가 필요한 시점에서 재개할 수 있는 특수한 함수이다.
제너레이터는 다음과 같은 특징이 있다.
제너레이터 함수는 function*
키워드로 선언한다. 그리고 하나 이상의 yield
표현식을 포함한다.
function* genFunc() {
yield 1;
}
function
키워드 바로 뒤에 *를 붙이는 것을 권장하며, 화살표 함수와 생성자 함수 이외에 모든 형태로 선언할 수 있다.
const genFunc = * () => {} // SyntaxError
function* GenFunc() {}
new GenFunc(); // TypeError
제너레이터 함수를 호출하면 일반 함수처럼 함수 코드 블록을 실행하는 것이 아니라, 제너레이터 객체를 반환한다. 이 객체는 이터러블 하면서 동시에 이터레이터이다.
즉, 제너레이터 객체는 Symbol.iterator
메서드를 상속받는 이터러블이면서 value
, done
프로퍼티를 갖는 이터레이터 리절트 객체를 반환하는 next
메서드를 소유하는 이터레이터이다.
게다가 이터레이터에 없는 return
, throw
메서드도 갖는다.
function* genFunc() {
try {
yield 1;
yield 2;
} catch(e) {
console.error(e);
}
}
const generator = genFunc()
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.return('return this')); // {value: 'return this', done: true}
console.log(generator.throw('error!')); // {value: undefined, done: true}
yield
표현식까지 차례로 코드 블록 실행 / yield
된 값을 value
프로퍼티 값으로, false
를 갖는 done
프로퍼티로 리절트 객체를 반환value
프로퍼티 값으로, true
를 갖는 done
프로퍼티로 리절트 객체를 반환undefined
를 value
프로퍼티 값으로, true
를 갖는 done
프로퍼티로 리절트 객체를 반환yield
키워드와 next
메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있다. 즉, 제너레이터 함수는 일반 함수와 다르게 함수 호출자에게 제어권을 양도하여 필요한 시점에서 함수 실행을 재개할 수 있다.
function* genFunc() {
console.log(1);
yield 1;
console.log(2);
yield 2;
console.log(3);
yield 3;
}
const generator = genFunc();
console.log(generator.next());
// 1
// {value: 1, done: false}
console.log(generator.next());
// 2
// {value: 2, done: false}
console.log(generator.next());
// 3
// {value: 3, done: false}
console.log(generator.next());
// {value: undefined, done: true}
이터레이터의 next 메서드와 달리 제너레이터 객체의 next 메서드는 인수를 전달할 수 있다. 바로 이 부분이 제너레이터의 실행 흐름을 헷갈리게 하는 요인이라고 생각하는데 실행 흐름은 다음과 같다.
제너레이터 객체의 next 메서드에 전달한 인수는 제너레이터 함수의 yield 표현식을 할당받는 변수에 할당된다. 단, yield 표현식을 할당 받는 변수에 yield 표현식의 평가 결과가 할당되는 것은 아니다.
function* genFunc() {
const a = yield 1;
const b = yield (a + 2);
// 제너레이터에서 return은 반환의 의미가 아닌 종료의 의미로 사용한다.
return a + b;
}
const generator = genFunc();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next(10)); // {value: 12, done: false}
console.log(generator.next(30)); // {value: 40, done: true}
실행 흐름을 보자. 첫 next()
에는 인자 값을 줄 수 없다. 주더라도 무시된다. next
메서드를 호출하면 첫 yield
표현식까지 실행하고 그 결과값을 value
프로퍼티에 담아서 준다.
두 번째 next
메서드를 실행할 때 인자로 10
을 담아서 넘겨주고 있다. 이 10이 b
에 할당될 것 같지만, a
에 할당된다.
그리고 value
프로퍼티에 a+2
가 10+2
가 되므로 12가 할당된다. b
에 12가 할당된다고 생각하면 안 된다.
마지막 next
메서드를 실행할 때 인자로 30을 담아서 넘겨준다. 이 30은 b
에 할당된다. 더 이상의 yield
문이 없으므로 함수 끝까지 실행되며 이 때의 return
값이 value
프로퍼티의 값이 된다. 즉 value
프로퍼티에 10+30
, 40이 할당된다.
이처럼 제너레이터는 가독성이 좋지 않다. 그래서 ES8부터는 async / await
를 도입하여 제너레이터를 대신하여 비동기를 동기처럼 작동하도록 구현했다.