[JS] 제너레이터
지난 포스팅에서 이터러블과 이터레이터에 대해서 살펴보았다.
또한 제너레이터라는 것을 사용하면 이터레이터를 편리하게 만들 수 있다는 것을 알게 되었다. 이번 포스팅에서는 제너레이터를 사용하여 이터레이터를 만들어보자.
▶ 제너레이터
- 제너레이터는 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능을 가진 함수를 말한다.
-
반복할 수 있는 이터레이터 객체를 생성한다.
- *가 붙은 함수가 제너레이터 함수이다.
- 제너레이터 함수는 실행하면, Iterator 객체가 반환(next()를 가지고 있음)된다.
- iterator 은 객체는 next 메서드로 순환 할 수 있는 객체이며, 비동기 작업이 완료되는 시점마다 next 메서드를 호출해주면 Generator 함수 내부소스가 위 -> 아래로 순차적으로 진행된다.
▷ 일반함수 vs 제너레이터
제너레이터가 일반 함수와 다른 점은 함수의 실행을 제어할 수 있다는 점이다.
- 단 한 번의 실행으로 함수의 끝까지 실행이 완료되는 일반 함수와는 달리, 제너레이터 함수는 사용자의 요구에 따라 (yield와 next를 통해) 일시적으로 정지될 수도 있고, 다시 시작될 수도 있다.
- 일반 함수는 호출이 되면 함수의 코드 블록을 실행시키지만, 제너레이터 함수는 제너레이터 객체를 생성해서 반환한다.
- 일반 함수는 하나의 값(혹은 0개의 값)만 반환하지만 제너레이터를 사용하면 여러 개의 값을 필요에 따라 하나씩 반환(yield)할 수 있다.
그렇다면 제너레이터 객체란 무엇일까?
▷ 제너레이터 객체와 이터러블
제너레이터 함수를 호출했을 때 리턴되는 객체를 제너레이터 객체라고 한다. 여기서 객체는 이터러블이면서 동시에 이터레이터이다.
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
// generator는 iterator를 반환한다.
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
따라서 for-of
반복문을 사용해 값을 얻을 수 있다.
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for (let value of generator) {
alert(value); // 1, 2
}
⚠️ 위 코드에서 3은 출력되지 않는다. 왜 그럴까?
for-of 이터레이션이 done: true일 때 마지막 value를 무시하기 때문이다. 그러므로 for-of를 사용했을 때 모든 값이 출력되길 원한다면 yield로 값을 반환해야 한다.
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for (let value of generator) {
alert(value); // 1, 2, 3
}
▷ 이터레이터 구현
제너레이터를 사용하지 않고 이터레이터(객체의 반복)를 구현해보자
function createIterator(items) {
let i = 0;
return {
next: function () {
const done = i >= items.length;
const value = !done ? items[i++] : undefined;
return {
done: done,
value: value,
};
},
};
}
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
제너레이터를 사용하여 이터레이터(객체의 반복)를 구현해보자.
제너레이터 문법
- 제너레이터를 만들려면 제너레이터 함수라 불리는
function*
이 필요하다.- function 다음에 붙은 별표* (= 에스터리스크)는 이 함수가 제너레이터 함수라는 의미이다.
- 제너레이터 함수는
yield
키워드를 만나면 해당 지점에서 동작을 멈추며 yield 키워드 뒤에 지정된 값을 반환한다. - 제너레이터는 이터러블이므로
next()
함수를 사용하여 제너레이터 함수를 호출할 수 있다.
function* createIterator() {
yield 1;
yield 2;
yield 3;
}
// generator는 iterator를 반환한다.
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
확실히 제너레이터를 사용한 쪽이 코드의 가독성, 간결성면에서 우수하다고 할 수 있다.
- 또한 제너레이터 함수는 본문에 return문을 작성하여 함수의 동작을 끝낼 수 있다.
function* generatorFunc(n) {
yield n;
return n + 1; // 제너레이터 종료
yield n + 2; // 동작 안함
}
const generatorObj = generatorFunc(1);
console.log(generatorObj.next());
console.log(generatorObj.next());
console.log(generatorObj.next());
실제 예제
// (1) 제너레이터 함수 안에서 쓸 addCoffee 함수 선언
var addCoffee = function (prevName, name) {
setTimeout(function () {
coffeeMaker.next(prevName ? prevName + ", " + name : name);
}, 500);
};
// (2) 제너레이터 함수 선언
// yield 키워드로 순서 제어
var coffeeGenerator = function* () {
var espresso = yield addCoffee("", "에스프레소");
console.log(espresso);
var americano = yield addCoffee(espresso, "아메리카노");
console.log(americano);
var mocha = yield addCoffee(americano, "카페모카");
console.log(mocha);
var latte = yield addCoffee(mocha, "카페라떼");
console.log(latte);
};
// coffeeMaker는 이터레이터 객체
var coffeeMaker = coffeeGenerator();
coffeeMaker.next();
📎참조
- https://velog.io/@ggong/ES6-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0%EC%99%80-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0
- https://developer-talk.tistory.com/274
- https://ko.javascript.info/generators
- https://seo-tory.tistory.com/77
댓글남기기