Generators, advanced iteration¶
Generators¶
<https://javascript.info/generators> のノート。
ジェネレーターとは、関数の特殊な形態であって、複数の値を次から次へと、必要に応じてすぐに返すものをいう。データストリームを作るのに有用だ。
Generator functions¶
ジェネレーターを定義するには専用の構文を書く:
function* generateSequence() {
// function body...
}
本体には
yield
文が現れる。ジェネレーター関数は、呼び出し時に通常の関数とは異なる動作をする。コードを実行せず、代わりに、ジェネレーターオブジェクトと呼ばれる特別なオブジェクトを返す。
ジェネレーターの主要メソッドは next()
だ。呼び出されると最も近い yield
文まで実行する。その後、関数の実行は一時停止し、yield
された値が呼び出し側のコードに返される。
メソッド next
が返すオブジェクトにはプロパティーが二つある:
Property |
Description |
---|---|
|
|
|
コードが完了したかどうか |
Generators are iterable¶
そういうわけで、ジェネレーターに for
… of
ループを適用できる。ただし、ループは return
の値を無視するので、ジェネレーター関数で return
文で値を返さないように定義するのがいい。
ジェネレーターに対して ...
を適用できる。配列に変換される。
Using generators for iterables¶
以前習った range
をジェネレーター関数に書き直す。
let range = {
from: 0,
to: 5,
// a shorthand for [Symbol.iterator]: function*()
*[Symbol.iterator]() {
for(let value = this.from; value < this.to; value++) {
yield value;
}
}
};
Generator composition¶
ジェネレーター合成とは、ジェネレーター同士を透過的に「埋め込む」ことを可能にする特別な機能だ。それには yield*
文を使う。
function* generatePasswordCodes() {
// 0..9
yield* range(48, 58);
// A..Z
yield* range(65, 91);
// a..z
yield* range(97, 123);
}
yield
is a two-way street¶
ジェネレーターは結果を呼び出し側に返すだけでなく、呼び出し側がジェネレーターの内部に値を渡すこともできる。引数を指定して generator.next(arg)
を呼び出せばいい。これが受信側 yield
文の戻り値となる。
<!– sequence diagram –>
generator.throw
¶
ジェネレーターには値だけでなく、エラーを送出することもできる。エラーオブジェクトを指定して generator.throw(err)
を呼び出せばいい。すると、受信側 yield
文で送出されたかのように働く。
例外処理ブロックは、ジェネレーター関数内部と呼び出し側のどちらかに置く可能性がある。用途による。
generator.return
¶
ジェネレーターを呼び出し側から停止させるには generator.return(value)
を呼び出す。
終了したジェネレーターに対して
generator.return
を使用すると、さっき指定した値が再び返される。多くの場合、これを使うことはないのだが、特定の条件で停止させたいときに便利だ。
Tasks¶
Pseudo-random generator¶
問題よりも Math.random
には seed を指定する機能がないという事実のほうが重要だ。
Async iteration and generators¶
<https://javascript.info/async-iterators-generators> のノート。
非同期反復処理では、非同期で送られてくるデータを必要なときにすぐに反復処理することができる。さらに、非同期ジェネレーターを使えばもっと便利になる。
Symbol.asyncIterator
を採用することで非同期的 iterable を定義する。メソッドnext()
をasync
とする。言い換えるとPromise
を返す。ループでそのような iterable を反復するときは
for await
文を用いる。async function*
構文で非同期 generator を定義する。
Recall iterables¶
以前習った、単なる反復版の range
を今一度確認する。
Async iterables¶
それを非同期版にする。コードを少々変更するだけでよい:
[Symbol.iterator]
を[Symbol.asyncIterator]
に置き換える。メソッド
next
をasync
にする。これでPromise
を返すようになる。呼び出し側のループは
for await
文を使う。
通常の同期反復を必要とする機能は、非同期反復では動作しない。
Recall generators¶
以前習ったジェネレーター版 range
を今一度確認する。
Async generators (finally)¶
それを非同期版にする。コードを少々変更するだけでよい:
ジェネレーター関数を
async
にする。これでPromise
を返すようになる。呼び出し側のループは
for await
文を使う。
ループではなく、手動で反復する場合には await generator.next()
と書く。
Async iterable range¶
*[Symbol.iterator]()
を async *[Symbol.asyncIterator]
のように書く。
Real-life example: paginated data¶
終わりが不明であるループから非同期関数 fetch
を呼び出すような状況では、非同期ジェネレーターパターンが普通に顔を出すと見た。