Generators, advanced iteration

Generators

<https://javascript.info/generators> のノート。

ジェネレーターとは、関数の特殊な形態であって、複数の値を次から次へと、必要に応じてすぐに返すものをいう。データストリームを作るのに有用だ。

Generator functions

ジェネレーターを定義するには専用の構文を書く:

function* generateSequence() {
    // function body...
}
  • 本体には yield 文が現れる。

  • ジェネレーター関数は、呼び出し時に通常の関数とは異なる動作をする。コードを実行せず、代わりに、ジェネレーターオブジェクトと呼ばれる特別なオブジェクトを返す。

ジェネレーターの主要メソッドは next() だ。呼び出されると最も近い yield 文まで実行する。その後、関数の実行は一時停止し、yield された値が呼び出し側のコードに返される。

メソッド next が返すオブジェクトにはプロパティーが二つある:

Property

Description

value

yield 文または return 文の値

done

コードが完了したかどうか

Generators are iterable

そういうわけで、ジェネレーターに forof ループを適用できる。ただし、ループは 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] に置き換える。

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