この記事の目的は、リアクティブプログラミングが必要な理由、それが関数型プログラミングとどのように関係するのか、それを使用して新しい要件に簡単に適応できる宣言型コードを記述する方法を説明することです。 さらに、実際に近い例でこれをできるだけ簡単かつ簡単に実行したいと思います。
次のタスクを実行します。
REST APIとエンドポイント/people
ユーザーを使用した特定のサービスがあります。 このエンドポイントに対してPOST要求が行われると、新しいエンティティが作成されます。 { name: 'Max' }
形式のオブジェクトの配列を受け取る関数を作成し、APIを使用してエンティティのセットを作成します(英語では、これはバッチ操作と呼ばれます)。
この問題を命令的なスタイルで解決しましょう。
const request = require('superagent') function batchCreate(bodies) { const calls = [] for (let body of bodies) { calls.push( request .post('/people') .send(body) .then(r => r.status) ) } return Promise.all(calls) }
比較のために、このコードを機能的なスタイルで書き直しましょう。 簡単にするために、機能的なスタイルを意味します。
- 命令ループ( for 、 while )の代わりに機能プリミティブ( .map 、 .filter 、 .reduce )の使用
- コードは「純粋な」関数に編成されます-それらは引数のみに依存し、システムの状態に依存しません
機能スタイルコード:
const request = require('superagent') function batchCreate(bodies) { const calls = bodies.map(body => request .post('/people') .send(body) .then(r => r.status) ) return Promise.all(calls) }
同じサイズのコードを取得しましたが、このコードが前のコードよりも優れているかどうかが明確でないことを認める価値があります。
コードの2番目の部分が優れている理由を理解するために、コードの変更を開始する必要があります。元のタスクに新しい要件が出現したと想像してください。
呼び出すサービスには、一定期間内のリクエスト数に制限があります。1秒で、1つのクライアントが実行できるリクエストは5つまでです。 さらにリクエストを実行すると、サービスは429 HTTPエラーを返します(リクエストが多すぎます)。
この場所では、おそらく、停止して自分で問題を解決しようとする価値があります。%username%
機能的なコードを基礎として、それを変更してみましょう。 「純粋な」関数型プログラミングの主な問題は、ランタイムと入出力(英語ではこれに対する副作用の表現があります)については何も「知らない」ことですが、実際には常にそれらと連携しています。
このギャップを埋めるために、リアクティブプログラミングが役立ちます。副作用の問題を解決しようとする一連のアプローチです。 このパラダイムの最も有名な実装は、 リアクティブストリームコンセプトを使用したRxライブラリです。
リアクティブストリームとは何ですか? 非常に簡単に言えば、これは、機能的なプリミティブ(.map、.filter、.reduce)を時間の経過とともに配布されるものに適用できるアプローチです。
たとえば、ネットワークを介して特定のコマンドセットを送信します。セット全体を取得するまで待つ必要はなく、リアクティブストリームとして提示し、それを操作できます。 ここでは、2つの重要な概念が発生します。
- フローは無限であるか、時間の経過とともに任意に分散することができます
- 送信側は、受信側がコマンドを処理する準備ができている場合にのみコマンドを送信します(バックプレッシャー)
この記事の目的は簡単な方法を見つけることです。そのため、Rxと同じ問題を解決しようとするHighlandライブラリを使用しますが、学習ははるかに簡単です。 内部の考え方は簡単です。Node.jsストリームを基礎として、あるストリームから別のストリームにデータを「転送」しましょう。
始めましょう:簡単なものから始めましょう-新しい機能を追加せずにコードを「リアクティブ」にします
const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .collect() .toPromise(Promise) }
次のことに注意してください。
- H(ボディ)-配列からストリームを作成します
- .flatMapおよびそれが受け入れるコールバック。 アイデアは非常に単純です-Promiseをストリームコンストラクターでラップして、1つの値(またはエラー)を持つストリームを取得します。これはPromiseではなく値であることを理解することが重要です。
結果として、これはフローのストリームを提供します-flatMapを使用して、操作可能な値の単一のストリームに平滑化します(誰がモナドと言いましたか?) - .collect-1つの「ポイント」内のすべての値を配列に収集する必要があります
- .toPromise-Promiseに戻ります。Promiseは、ストリームから値を取得した時点で満たされます
それでは、要件を実装してみましょう。
const request = require('superagent') const H = require('highland') function batchCreate(bodies) { return H(bodies) .flatMap(body => H(request .post('localhost:3000/people') .send(body) .then(r => r.status) ) ) .ratelimit(5, 1000) .collect() .toPromise(Promise) }
バックプレッシャーの概念のおかげで、これはこのパラダイムの.ratelimitの1行にすぎません。 Rxでは、ほぼ同じスペースが必要です。
それだけです、あなたの意見は興味深いです:
- 記事の冒頭で宣言した結果を達成できましたか?
- 命令型アプローチを使用して同様の結果を達成することは可能ですか?
- リアクティブプログラミングに興味がありますか?
PS:ここでは、リアクティブプログラミングに関する別の記事を見つけることができます