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 を呼び出すような状況では、非同期ジェネレーターパターンが普通に顔を出すと見た。