Generator 함수
제너레이터(Generator) 함수
Javascript에서는 동기와 비동기가 있다. 동기는 호출과 실행을 같이 하고 비동기는 호출과 실행을 같이 하지 않는다. 예를 들어 console.log(‘hello’)는 호출하자마자 스스로 ‘hello’를 출력한다. 하지만 hello()는 호출을 하지만 function hello(){ console.log(‘hello’); }에서 ‘hello’를 출력한다.
제너레이터 함수란 이런 비동기 함수를 좀 더 유용하게 사용할 수 있게 하는 함수이다.
간단한 예제와 함께 알아보자
function* call() {
console.log('첫번째');
yield 1; // 첫번째 호출
console.log('두번째');
yield 2; // 두번째 호출
console.log('세번째'); // 세번째 호출
}
const result = call();
result.next(); // 첫번째
result.next(); // 두번째
result.next(); // 세번째첫번째
두번째
세번째
제너레이터 함수는 호출 됐을 때 코드를 한번에 실행하지 않고 일시정지 했다가 필요한 시점에서 이어서 나머지 코드를 동작하는 방으로 작동한다.
.next()로 함수를 호출하면 코드에서 yield을 만날 때 실행을 멈추는 방식으로 동작한다.
한 번 호출하면 첫번째 yield까지 실행되고 멈추었다가 두번째 호출되면 첫번째에서 멈춘 곳 부터 두번째 yield을 만날 때까지 실행된다.
또 .next()로 호출할 때 result처럼 변수에 담지않고 호출한다면 호출된 횟수를 저장할 수 없어 몇 번을 호출해도 ‘첫번째’만 출력된다.
function* call() {
console.log('첫번째');
yield 1;
console.log('두번째');
yield 2;
console.log('세번째');
}
call().next(); // 첫번째
call().next(); // 첫번째
call().next(); // 첫번째첫번째
첫번째
첫번째
또 제너레이터 함수는 .next()로 호출할 때 {value: 1, done: false} 객체를 반환한다.
value는 yield에 할당한 값이고, done은 남은 코드 안에 yield이 있다면 false 없다면 true가 할당된다.
function* call() {
console.log('첫번째');
yield 1;
console.log('두번째');
yield 2;
console.log('세번째');
}
const result = call();
console.log(result.next()); // {value: 1, done: false}
console.log(result.next()); // {value: 2, done: false}
console.log(result.next()); // {value: undefined, done: true}첫번째
{value: 1, done: false}
두번째
{value: 2, done: false}
세번째
{value: undefined, done: true}
이러한 제너레이터 함수를 쓰는 이유는 무엇일까
가장 큰 이유는 콜백지옥(Callback hell)에 빠지지 않기 위해서이다.
예를 들어 setTimeout을 이용해 3초뒤 30을 출력후 다시 1초뒤 10을 출력, 마지막으로 2초뒤 20을 출력하여 30, 10, 20 순으로 출력하는 코드를 작성해본다고 하자
setTimeout(()=>{console.log('30')}, 3000);
setTimeout(()=>{console.log('10')}, 1000);
setTimeout(()=>{console.log('20')}, 2000);10
20
30
동기 함수라면 위의 코드로 원하는 값을 얻을 수 있을 것이다. 그러나 setTimeout은 비동기 함수이기 때문에 호출한 순서와 출력된 순서가 맞지않아 원하는 값을 얻을 수 없다.
setTimeout(()=>{
console.log(30);
return setTimeout(()=>{
console.log(10);
return setTimeout(()=>{console.log(20);}, 2000);
}, 1000);
}, 3000);30
10
20
원하는 값을 얻기 위해서는 위의 예제처럼 콜백 안에 콜백을 사용해 코드를 작성해야한다. 하지만 이런 콜백함수가 반복되면 가독성이 떨어지고 난해한 코드가 되어 콜백지옥에 빠지게된다.
이 예제에 제너레이터 함수를 적용해보자
function* setime() {
setTimeout(()=>{console.log('30'); gen.next();}, 3000);
yield 30;
setTimeout(()=>{console.log('10'); gen.next();}, 1000);
yield 10;
setTimeout(()=>{console.log('20');}, 2000);
}
const gen = setime();
gen.next();30
10
20
제너레이터 함수를 적용한 결과이다. 눈으로만 보기에도 제너레이터 적용 전의 함수보다 가독성이 높아졌다.
또한 제너레이터 함수에 매개변수를 넣을 수도 있다.
function* gen(n) {
let x, y;
x = yield n;
y = yield n;
console.log(x+y); // 3
}
let generator = gen();
generator.next(); // 제너레이터 함수 시작
generator.next(1);
generator.next(2); 제너레이터 함수 사용법
- 제너레이터 함수
function* genFun() {
yield 1;
}
let generator = genFun();- 제너레이터 메소드
let genObj = {
* genMethod() {
yield 2;
}
};
let generator = genObj.genMethod();- 제너레이터 클래스 메소드
class MyClass {
* genClsMethod() {
yield 3;
}
}
let genClass = new MyClass();
let generator = genClass.genClsMethod();