여러 개의 값을 필요에 따라 하나씩 반환할 수 있는 함수입니다.
제너레이터 함수는 function*
을 통해 만들 수 있고,
함수를 호출할 때 코드가 실행되는 것이 아니라 실행을 처리하는 특별 객체 '제너레이터 객체'가 반환됩니다.
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
next
제너레이터의 주요 메서드로써 호출 시 가장 가까운 yield <value>문을 만날 때까지 실행합니다.
value가 없으면 undefined를 반환하며 아래와 같은 구조로 되어 있습니다.
계속 넥스트 메서드를 사용하다가 리턴문을 만나면 끝나거나, yield를 다 돌고나서 마지막에 끝납니다.
{value: 1, done: false}
(1) 제너레이터는 이터러블이므로 for..of문을 사용할 수 있습니다.
주의할 점은 제너레이터 객체의 done이 true이면 for..of문은 무시하므로 return으로 값이 끝나지 않고 yield로 처리해야 합니다.
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1, 2, 3
}
(2) 전개문법 사용이 가능합니다.
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
제너레이션 함수 안에 다른 제너레이션 함수를 중복해서 사용할 수 있습니다.
yield*
지시자는 실행을 다른 제너레이터에 위임해 줄 수 잇습니다.
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
제너레이터 함수는 값을 안과 밖으로 전달할 수 있습니다.
function* gen() {
let ask1 = yield "2 + 2 = ?";
alert(ask1); // 4
let ask2 = yield "3 * 3 = ?"
alert(ask2); // 9
}
let generator = gen();
alert( generator.next().value ); // "2 + 2 = ?"
alert( generator.next(4).value ); // "3 * 3 = ?"
alert( generator.next(9).done ); // true
next로 처음 호출할 때만 값을 넣지 않고, (넣어도 무시될 겁니다) 이후에는 값을 넣어서 yield에 값을 줄 수 있습니다.
이와 같은 현상으로 에러도 던질 수 있습니다.
// (1)
function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("위에서 에러가 던져졌기 때문에 실행 흐름은 여기까지 다다르지 못합니다.");
} catch(e) {
alert(e); // 에러 출력
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("데이터베이스에서 답을 찾지 못했습니다.")); // (2)
// (2)
function* generate() {
let result = yield "2 + 2 = ?"; // Error in this line
}
let generator = generate();
let question = generator.next().value;
try {
generator.throw(new Error("데이터베이스에서 답을 찾지 못했습니다."));
} catch(e) {
alert(e); // 에러 출력
}
비동기로 제너레이터를 사용하는 방법에 대해 알아봅니다.
비동기 이터레이터는 아래의 특성을 갖춰야 합니다.
let range = {
from: 1,
to: 5,
// for await..of 최초 실행 시, Symbol.asyncIterator가 호출됩니다.
[Symbol.asyncIterator]() { // (1)
// Symbol.asyncIterator 메서드는 이터레이터 객체를 반환합니다.
// 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
// 다음 값은 next()에서 정해집니다.
return {
current: this.from,
last: this.to,
// for await..of 반복문에 의해 각 이터레이션마다 next()가 호출됩니다.
async next() { // (2)
// next()는 객체 형태의 값, {done:.., value :...}를 반환합니다.
// (객체는 async에 의해 자동으로 프라미스로 감싸집니다.)
// 비동기로 무언가를 하기 위해 await를 사용할 수 있습니다.
await new Promise(resolve => setTimeout(resolve, 1000)); // (3)
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
for await (let value of range) { // (4)
alert(value); // 1,2,3,4,5
}
})()
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// await를 사용할 수 있습니다!
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
let generator = generateSequence(1, 5);
for await (let value of generator) {
alert(value); // 1, 2, 3, 4, 5
}
})();
// async generator의 next 호출
result = await generator.next(); // result = {value: ..., done: true/false}
Symbol.asyncIterator를 generator로 구현하기
let range = {
from: 1,
to: 5,
async *[Symbol.asyncIterator]() { // [Symbol.asyncIterator]: async function*()와 동일
for(let value = this.from; value <= this.to; value++) {
// 값 사이 사이에 약간의 공백을 줌
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for await (let value of range) {
alert(value); // 1, 2, 3, 4, 5
}
})();