コールバック地獄よさようなら。Node-REDで非同期処理を使いこなせ!
Javascriptは非同期イベントドリブン処理が行える効率の良い処理系ですが、いわゆるコールバック地獄という見通しが悪くなる問題があります。Node.js上のビジュアル開発環境であるNode-REDは、機能モジュールであるノードをつなぐことで簡単に非同期処理を扱う事ができます。
ここでは、順次、 並列、待ち合わせといった基本的な処理をNode-REDでどのように書くかを説明します。
非同期イベントドリブン処理のイメージ
お湯を沸かしながらTVを見ることを考えてみます。やかんにお湯を入れて火をつけてアイロンを始めますが、アイロンに集中しすぎて空焚きになる事があります。
そこで、キッチンタイマーを1分ごとにセットして、タイマーがなるごとにチェックします。すると空焚きは防げますが、TVの内容がよくわかりません。マルチタスクは難しいのです。
では、やかんを笛吹きケトルに変えてみます。お湯が沸騰すると笛の音が鳴りますので、都合の良い所で火を消します。音が鳴るまでは気にせずにTVを見ている事ができます。
このように待ち時間がかかる処理の場合に、処理が終わるまでCPUを解放するのが非同期イベントドリブン処理です。タスク(計算機で言うとスレッド、プロセス、コンテナ)を定期的に切り替え無くても良いので効率的に計算機を使えます。
コールバック地獄
Javascriptはこのように効率的な非同期イベントドリブン処理の言語です。しかし、ケトルの笛にあたるコールバックを記述する際にネストが深くなるという欠点があります。
これは、コールバック地獄と呼ばれるもので、検索してみると多くの人が苦しんでいる様子が見えます。そこで、かつては async.js などの非標準ライブラリで順次処理や並列処理の待ち合わせが行われていました。
信じる者も救われるか微妙なPromise
Node.js v0.12 から Promise が標準で使えるようになりました。Promiseは処理の待ち合わせを実現するオブジェクトです。呼ばれた関数が先にオブジェクトを渡しておいて処理終了後にに状態を変える事で、オブジェクトを受け取った呼び出し側がコールバック処理に相当する次の処理が行えます。
Promise は良くできた仕組みで、コールバック地獄を無くします。しかし、少し複雑な処理を書こうとすると、Promiseアンチパターン - くじら公園 などを参考にウンウンうなる事になります。
また、Promiseはデータを受け渡す事ができますが、下流の方の処理に必要なデータを途中の処理すべてが受け渡さないといけません。全体の構造を考えたり、作り直すだけで疲れてしまいます。
そこでNode-RED
以前、このブログでも紹介した Node-RED はNode.js上で動作しますが、様々な処理を簡単に書く事ができます。
順次処理
ノードをつなぐと順次処理になります。前のノードの処理がreturnしたメッセージオブジェクトが、次のノードの入力となります。
結果:
Ans: 0
前段のファンクションノードの記述
msg.payload = 0; return msg;
同一データ個別並列処理
ノードの出力を複数のノードにつなぐと並列に実行されます。もちろん非同期ですので実行順序は保証されません(以降の並列処理も同じです)。
結果:
Ans: 0
答: 0
前段のファンクションノードの記述(新しいオブジェクトを作っていますが、順次処理の記述と効果は同じです。このフローでは問題ありませんが、msgオブジェクトの他のデータが引き継がれないので気をつけてください)
return {payload: 0};
個別データ個別並列処理
ノードの出力の数を増やすと、それぞれのデータが並列に実行されます。
結果
Ans: 0 答: 1
前段のファンクションノードの記述
return [{payload:0}, {payload:1}];
個別データ同一並列処理(node.send)
ファンクションノードで node.send ()を使うと、複数のブジェクトを非同期出力できます。それぞれのデータが並列に実行されます。
結果
Ans: 0 Ans: 1 Ans: 2 Ans: 3 Ans: 4
前段のファンクションノードの記述
for (var i = 0; i < 5; i++) { node.send({payload: i}); }
個別データ同一並列処理(split)
string, array, objectをsplitノードに渡すと、オブジェクトを分解してnode.send()できます(stringは区切り文字を指定できます)。インジェクトノードで配列を渡しています。
結果
Ans: 0 Ans: 1 Ans: 2 Ans: 3 Ans: 4
待ち合わせ
splitノードで並列化した場合、joinノードで待ち合わせできます。msg.payloadのデータはまとめられます。
結果
[ "Ans: 0", "Ans: 1", "Ans: 2", "Ans: 3", "Ans: 4" ]
おわりに
Node-REDの様々な処理をまとめました。Node-REDは順次処理が基本になっていますので、javascriptに慣れていない方でも様々な処理を簡単に記述できます。
Node-REDはIoTの基盤として知られていますが、httpやwebソケットを含めて様々なプロトコルが扱えますし、Dashboardを使えば簡易なUIであれば簡単に作成できます。ちょっとしたテスト用のサーバなら、容易に構築できます。
Node-REDはJavascriptだからと敬遠されている方もおられるかも知れませんが、ぜひ一度、触ってみてください。
おまけ:非同期あるある
Node-REDのファンクションノードでAWS-SDKなどの非同期処理を呼ぶ場合、非同期処理のコールバック関数のreturnでは次のノードにデータを渡す事ができません。このような場合は、継続するノードが順次処理であってもnode.send()でデータを送ってください。
ちなみにAWS-SDKを呼ぶオーソドックスな方法は、settings.js内でrequireしたものをfunctionGlobalContextにセットしておいてファンクションノード内で利用します。
参考:https://nodered.org/docs/configuration#node-configuration
この記事は、SRA Advent Calendar 2016 の12月24日の記事です。
« ポケモンGOで考えるリスクマネージメント(3/3) WF、アジャイル、CCPM | トップページ | メンバーに良い成績を付ける - リーダーのベンチマーク - »
「Node-RED」カテゴリの記事
- アイデアをシームレスに実装する - 考える道具としてのNode-RED -(2020.12.13)
- [#Node-RED] ファンクションノードのデバッグどうしてる?(2019.12.16)
- Node-RED: joinノードでタイムアウト処理(2018.12.25)
- Node-REDで世界が変わる!(2018.12.01)
- デブサミ関西でNode-REDとペンギンと勇気の話をしました #devsumiB(2018.10.28)
この記事へのコメントは終了しました。
コメント