カテゴリー
 【Rxjs基礎講座】GeneratorをObservableへ変換する方法  
※ 当ページには【広告/PR】を含む場合があります。
2020/07/17
 2022/10/05 
以前の
async/awaitもっとニッチなところで、
今回は
generatorGeneratorの簡単なおさらい
「
javascript generatorなのでここでは深く説明しませんが、ECMAScript2015(ES6)から盛り込まれた機能で、ES7から正式に追加された
async/awaitジェネレーターは一見すると従来のイタレーターの拡張版に見えます。
しかし単なるイタレーターとは違って、遅延評価・同期処理・関数外部からの制御・無限シークエンス表現...などのテクニックを提供してくれる便利なものです。
typescriptではジェネレーターは
Generator<T>例えば簡単な実装で遊んでみますと、
            function* generator(): Generator<any> {
    yield 0;
    let i = 4;
    yield 1;
    yield 'HOGE';
    yield 2 + i;
    i++;
    yield 'PIYO';
    yield 1.0e-3 * i;
    yield false;
    yield 'FUGA';
    yield i - 3;
    yield true;
}
const iterator = generator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
        これをビルドして実行すると、
            $ node dist/index.js
{ value: 0, done: false }
{ value: 1, done: false }
{ value: 'HOGE', done: false }
{ value: 6, done: false }
{ value: 'PIYO', done: false }
{ value: 0.005, done: false }
{ value: false, done: false }
{ value: 'FUGA', done: false }
{ value: 2, done: false }
{ value: true, done: false }
{ value: undefined, done: true }
{ value: undefined, done: true } # 処理が終わってもGeneratorは消滅せずに残る
        というような出力を得ます。
見てお分かりのように、ジェネレーター内部で
yieldnext()そのときの返り値は
{value: any, done: boolean}valuedoneGeneratorからObservableへの変換
それでは早速、先程のジェネレーターを元にして、Observableに変換した処理を行ってみます。
検証① ~ fromオペレーターから
別のブログ記事で以前からfromオペレーターで
実のところ、
fromオペレーター            import { Observable, from } from 'rxjs';
import { take } from 'rxjs/operators';
function* generator(): Generator<any> {
    yield 0;
    let i = 4;
    yield 1;
    yield 'HOGE';
    yield 2 + i;
    i++;
    yield 'PIYO';
    yield 1.0e-3 * i;
    yield false;
    yield 'FUGA';
    yield i - 3;
    yield true;
}
// 👇fromオペレーターからジェネレーターもObservableへ一発変換
const iterator$ = from(generator()).pipe(
    take(11)
);
iterator$.subscribe(
    res => console.log(res),
    err => console.log(err),
    () => console.log('DONE!')
);
        これをビルドして実行すると、
            $ node dist/index.js
0
1
HOGE
6
PIYO
0.005
false
FUGA
2
true
DONE! # Generatorの処理が終わったらtakeでUnsubscribe
        このようにジェネレーターの返り値の内
valuenext()また、ジェネレーターが
donetruecomplete()検証② ~ deferオペレーターから
Fromオペレーターが使えるのなら、
defer以下試してみましょう。
            import { Observable, defer } from 'rxjs';
import { take } from 'rxjs/operators';
function* generator(): Generator<any> {
    yield 0;
    let i = 4;
    yield 1;
    yield 'HOGE';
    yield 2 + i;
    i++;
    yield 'PIYO';
    yield 1.0e-3 * i;
    yield false;
    yield 'FUGA';
    yield i - 3;
    yield true;
}
// 👇deferオペレーターからジェネレーターもObservableへ一発変換
const iterator$ = defer(generator).pipe(
    take(11)
);
iterator$.subscribe(
    res => console.log(res),
    err => console.log(err),
    () => console.log('DONE!')
);
        とすることで、先程の結果と同じ結果を得ます。
fromオペレーターとの違いは、fromのほうが引数に
Generator<any>ジェネレーター関数応用編 ~ mergeMap(flatMap)のresultSelector(第二引数)を理解する
上記まででfromオペレーターやdeferオペレーターがGenerator型の引数をカバーしてることを示したました。
map系のオペレーターも第一引数にGenerator関数を指定することで利用可能です。
ここではmap系も種類が多いのですべてのオペレーターを個別に使用例をご紹介することができません。
丁度いい機会ですので、とりわけmap系オペレーターの持つ謎多き第二引数
resultSelectorresultSelector
関数の定義の部分だけ抜粋すると、
            Projects each source value to an Observable which is merged in the output Observable.
mergeMap<T, R, O extends ObservableInput<any>>(
    project: (value: T, index: number) => O,
    resultSelector?: number |
        ((outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number) => R),
    concurrent: number = Number.POSITIVE_INFINITY
): OperatorFunction<T, ObservedValueOf<O> | R>
Parameters:
    project:
        A function that, when applied to an item emitted by the source Observable,
        returns an Observable.
    resultSelector:
        Optional. Default is undefined.
        Type:
            number |
            ((outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number) => R).
    concurrent:
        Optional.
        Default is Number.POSITIVE_INFINITY.
        Maximum number of input Observables being subscribed to concurrently.
Returns:
    OperatorFunction<T, ObservedValueOf<O> | R>:
        An Observable that emits the result of applying the projection function
        (and the optional deprecated resultSelector) to each item emitted
        by the source Observable and merging the results of the Observables
        obtained from this transformation.
        そもそも単に
mergeMapresultSelectorもし必要に迫られ、mergeMapの複数の内部処理を可能な限りカスタマイズする場合には、
resultSelectorresultSelectorの挙動を探るため、mergeMapにジェネレーターを噛ませた以下のようなテストコードでその挙動を探ってみましょう。
            import { Observable, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
function genWithMergemap$(): Observable<any> {
    return of(1,5,8).pipe(
        mergeMap(
            (x: any, i: number) => (function* () {
                yield x;
                yield i;
            })(),
            // (outerValue: T, innerValue: ObservedValueOf, outerIndex: number, innerIndex: number)
            // (outerValue) oV: 1 --> 5 --> 8 (member in array of "of")
            // (innerValue) iV: return a member in array as the the generator has observed.
            // (outerIndex) oI: 0 --> 1 --> 2 (index of member in the array in "of").
            // (innerIndex) iI: 0 --> 1 (index that the generator rolls inside).
            (oV: any, iV: any, oI: number, iI: number) => {
                return `oV: ${oV}, iV: ${iV}, oI: ${oI}, iI: ${iI}`;
            }
        )
    );
}
const source = genWithMergemap$();
source.subscribe(
    x => console.log('Next: | %s |', x),
    e => console.log('Error: %s', e),
    () => console.log('Completed')
);
        これをビルドして実行してみますと、
            $ node dist/index.js
Next: | oV: 1, iV: 1, oI: 0, iI: 0 |
Next: | oV: 1, iV: 0, oI: 0, iI: 1 |
Next: | oV: 5, iV: 5, oI: 1, iI: 0 |
Next: | oV: 5, iV: 1, oI: 1, iI: 1 |
Next: | oV: 8, iV: 8, oI: 2, iI: 0 |
Next: | oV: 8, iV: 2, oI: 2, iI: 1 |
Completed
        ...と
resultSelectormergeMap(flatMap)オペレーターは1つの主ストリームから、また別のサブストリーム一つと合成して、新しいストリームを生成して流す仕組みです。

※出典: Rxjs/mergeMap APIリファレンスページ
https://rxjs-dev.firebaseapp.com/api/operators/mergeMap公式ページの説明図でいうと、
「1 --> 3 --> 5 --> ...」「10 --> 10 --> 10 --> ...」初学者向けの説明とはいえこの図だと、1つのストリームがもう一つ別のストリームと掛合わさって、新しい1つのストリームに合流するような印象を与えそうな気がします。
ですが実際は、前回の記事・
この図でいうと、
「1 --> 3 --> 5 --> ...」「10 --> 10 --> 10 --> ...」つまりは下流に流されるストリームの数は$$3 \times 3 = 9$$です。
再び上記のソースコードに目を向けると、
of「1 --> 5 --> 8 --> ...」今回はmergeMapの第一引数をジェネレーター関数で指定して利用します。
この場合、
yeildこれは、Innerストリームは2回相当となります。
ストリーム総数だけでいうと、$$3 \times 2 = 6$$回処理が行われています。
Innerストリーム
コードの中身を詳しく見ていきます。
まず、mergeMapの第一引数(
projectジェネレーター指定する場合には、
(value: T, index: number) => Oここでの
O            //....
(x: any, i: number) => (function* () {
    yield x;
    yield i;
})()
//...
        という形になっています。
こうすると、
yieldまた、
projectInnerストリームOuterストリーム
元となったベースのObservableから生成された流れです。
今回のコードでは
of(1,5,8)resultSelectorのマトリックス表記
resultSelectorの4つの引数である
(outerValue: T, innerValue: ObservedValueOf<O>, outerIndex: number, innerIndex: number)outerValue1,5,8outerIndex対して
innerValueここでの
innerIndexyieldyield x;0yield i;1innerValueyield今回でいうと
yieldxiでは、
outerValueEq. (1)
outerValueからすると、innerIndexがどうあれ値としてはOuterストリームの方の値が常に参照されます。
innerValueEq. (2)
こちらはouterValueの時と違い、
innerIndex0yield x;1yield i;まとめ
以上までで、mergeMapでの
resultSelectorこれは
project