概要
yieldおよびジェネレータ関数について勉強したことをまとめてみます。
yieldとは
- Javascriptにおけるジェネレータ関数で処理を一時停止したり再開したりするのに使われるもの
- 一般的な関数のreturnに相当するもので、ジェネレータ関数ではyieldを使用する
- returnと違って関数内で何回でも呼び出すことができる
- 参考:yield
ジェネレータ関数(Generator)とは
- イテレータを生成する関数
- イテレータとは配列のような繰り返し処理できるもののこと
- 書き方
/* ジェネレータ */
function* testGenerator() {
for(let i=1; i<=10; i++){
if (i % 2 == 0)
yield i;
}
}
// イテレータを生成
g = testGenerator()
// 繰り返し処理
for (let even of g) {
console.log(even);
}
// next()を使用する場合
// 上記繰り返し処理ですべての要素を呼び出したので、もう一度作り直す
g = testGenerator()
// next()の返り値は { value: 2, done: false } のようになっている
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 4, done: false }
console.log(g.next()); // { value: 6, done: false }
console.log(g.next()); // { value: 8, done: false }
console.log(g.next()); // { value: 10, done: false }
console.log(g.next()); // { value: undefined, done: true } valueがundefinedになってdoneは初めてtrueになる
あらためてyieldとは
- ジェネレータ関数.next()では
- 一回目の呼び出しは最初のyieldで一旦処理をストップ。
- 二回目は最初のyieldから二回目のyieldまでを処理しストップ。
- 三回目は...(以下繰り返し)となる。
function* testGenerator() {
console.log('yield 1!');
yield 1;
console.log('yield 3!');
yield 3;
console.log('yield 5!');
yield 5;
}
g = testGenerator()
console.log(g.next().value);
console.log(g.next().value);
console.log(g.next().value);
// または
// for (let odd of g) {
// console.log(odd);
// }
// 結果
// yield 1!
// 1
// yield 3!
// 3
// yield 5!
// 5
yield*
- ジェネレータ関数内で別のジェネレータ関数を呼び出す際に使用する
- next()を使用した際の返り値は「呼び出したジェネレータ関数の順」になる
function* testGenerator() {
yield 1;
yield 3;
yield 5;
}
function* testGenerator2() {
yield 2;
yield 4;
yield 6;
}
function* parentGenerator() {
yield* testGenerator();
yield* testGenerator2();
}
g = parentGenerator()
for (let num of g) {
console.log(num);
}
// 結果
// 1
// 3
// 5
// 2
// 4
// 6
ジェネレータ関数に引数を渡したい
- ジェネレータ関数には引数を渡せます。
- 渡した引数は関数内のどこからでも参照できます。
function* testGenerator(val) {
console.log(val);
yield 1;
console.log(val + 1);
}
let g = testGenerator(123);
g.next();
g.next();
// 結果
// 123
// 124
- さらにジェネレータ関数.next(引数)とすることで渡すこともできます
- 渡した引数はyield時に取り出すことができます。
function* testGenerator() {
let result = yield 1;
yield result + 2;
}
let g = testGenerator();
console.log(g.next(g.next().value).value);
// 結果
// 3
yieldを使った同期処理・非同期処理
- yieldの「処理を一時停止したり、再開したりする」機能を用いて非同期処理を同期的に処理することができます。
- 例えばこちらの関数
function async(){
let count = 1;
setTimeout(() => {
console.log(count++);
setTimeout(() => {
console.log(count++);
setTimeout(() => {
console.log(count++);
},1000)
},1000)
},1000)
}
async();
// 1秒毎に1, 2, 3と表示される
yieldを使えば以下のように書き換えられます
function async(g, num){
setTimeout(() => {
console.log(num);
g.next();
}, 1000)
}
g = (function* () {
yield async(g, 1);
yield async(g, 2);
yield async(g, 3);
})();
g.next();
元々ネストを浅くする目的で使われていたようですが、現在はasync, awaitが主流のようです。
function test(num){
return new Promise(resolve => {
setTimeout(() => {
console.log(num);
resolve();
}, 1000)
});
}
async function g(){
await test(1);
await test(2);
await test(3);
}
g();
疑問
ジェネレータ関数内でreturnするとどうなるか
function* testGenerator() {
yield 1
yield 2;
return 'return';
console.log('hello');
yield 3;
}
g = testGenerator();
console.log(g.next());
console.log(g.next());
console.log(g.next());
console.log(g.next());
// 結果
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 'return', done: true }
// { value: undefined, done: true }
- returnを呼び出した時点でdone: trueとなる
- return以降の処理は例えyieldの記載があっても呼ばれることはない。