前回の 第一歩 を投稿した後、以下のようなページや本で学習してみました。
- Nodeの作成 | Node-RED User Group Japan
- 目からウロコ!Node-REDのデザインパターン10選
- Node-REDのノードをつくる手順
- node-redプログラミング:カスタムノードを作成するときのTips
- Node‐REDユーザーグループ さんの本 はじめてのNode‐RED [改訂版]」
少し周りが見えるようになった第二歩は、配列を加工して遊んでみたいとおもいます。環境は Node-RED v0.19.2です。
なぜ配列か?
フツーのプログラミングだと、配列のように複数の要素を対象にした処理は「ループ」で処理を繰り返すことが多いとおもいます。
あれ Node-RED ってループってあるんだっけ?そもそも複数の値って、ノード間をどうやって流れていくの?と不思議になり、ちょっと試したくなった、ということでネタにしてみました。
フローの新規追加
前回 に作成したフローは一旦忘れて、心機一転、新しいフローを作って始めましょう。
右上のメニューから「フローを新規追加」し、新しく空っぽのフローを作成します。またその下の「フロー名を変更」で適切なフロー名を設定しておきましょう。
まずはシンプルに
Node-RED で一番シンプルな例は「injectノードと debugノードを並べ、それを接続したもの」のようですね。以下のような感じです。
これをデプロイした後、フロー画面でinjectノードの以下の部分をクリックすると!
右にあるデバッグコンソールに、謎の数値 (実はタイムスタンプ値) が表示される、というわけ。
さて、お約束みたいなので、injectノードの設定を少し変更してみます。ペイロード欄の形式を「文字列」に変更し、適当な文字列 (今回は: Hello Node-RED.) を入力します。また名前を「Hello」に変更しておきましょう。
名前の変更が反映されたことを確認して、再び「デプロイ」します。
そしてフロー画面でinjectノードを再びクリックし、デバッグコンソールに指定した文字列がちゃんと出力されていることを確認しましょう。
配列を出力してみよう
さきほどと同じように、injectノードの設定を変更します。
ペイロード欄の形式に配列は無いようなので「JSON」を選択して、配列データを以下のようなJSON形式で入力します。また名前も一応、「1-5」に変更しています。
[1,2,3,4,5]
デプロイしてinjectノードをクリックし、ちゃんと配列として出力されていることを確認します。
それぞれの値を3倍するには
さて、このサンプルをもとに、配列の中の値を3倍する方法を考えましょう。
普通の JavaScript だと、例えばループを使って要素を順に処理する場合は、以下のような感じになりますね。
var list = [1,2,3,4,5];
for (var l=0; l<list.length; l++) {
list[l] *= 3;
}
console.log(list);
最近は Stream API っぽい記法として、以下のように書くことも多いです。
var list = [1,2,3,4,5];
list = list.map(v => v * 3);
console.log(list);
どちらにしても、配列の要素に対して、順に決まった処理を実施していることは同じです。さて、フロー型の Node-RED では、こういった処理をどう記述したら良いのでしょうか?
まずは値ごとに分離してみる
とりあえず試してみましょう。さきほどのサンプルで、debugノードを少し右に移動し、パレットから選んだ Split ノードをその接続のうえまでドラッグして追加します。
すると接続を維持したまま Split ノードが追加されます。便利ですね。
デプロイしてinjectノードをクリックすると、配列の値がそれぞれ別に出力されることが確認できます。
次に値ごとに3倍してみる
計算をするので Function ノードですかね。さっそく Split ノードの後に追加してみましょう。
function ノードの設定を開き、名前を「* 3」に変更します。またコードの部分ですが、もともと
return msg;
となっていたコードの前に、以下のように msg.payload の値を3倍する処理を追加します。
msg.payload *= 3;
return msg;
設定後のフローはこんな感じ。
デプロイしてinjectノードをクリックすると、配列の値がそれぞれ3倍されて出力されることが確認できます。
最後はひとつの配列にまとめます
個々の値の計算はできたので、あとは配列に戻すだけですね。Split ノードと対になるノード、join ノードを function ノードの後に追加しましょう。
こんな感じで、特に設定はいりません。デプロイしてinjectノードをクリックすると、結果がちゃんと配列にまとめられているのが確認できます。
これで配列の中身を3倍するフローが完成ですね!
モヤモヤします
配列の値をそれぞれ、ちゃんと3倍できました!これでハッピーエンドとしたいのですが。駄目です、私には大きなモヤモヤが残ってしまいました。
join ノードの動きが謎です。
join ノードは、フローを流れてくる値をまとめてくれます。それは良いとして。でも単にフローの中で待っている join ノードなのに、どうして間違いなく5個まとめることができたのでしょうか?
私が超高速で injectノードを2回クリックしたとき、10個の配列ではなく、ちゃんと5個の配列を2つ返せたのは何故なのか?
debug してみよう
疑問に思ったら調べてみましょう。debug ノードを split ノードの直後に追加し、表示対象を「msg全体」に変更します。
デプロイしてinjectノードをクリックし、表示内容を確認してみましょう。以下は配列の最初の要素に対する出力です。
受け渡される msg オブジェクトに、parts というオブジェクトが追加されているのがわかります。このオブジェクトが元の配列の情報を保持していて、join ノードはこの値を参照して配列の操作をしていることが想像できます。
意地悪してみよう
join ノードが parts オブジェクトを参照しているか、ちょっと意地悪をして試してみましょう。function ノードに記載したコードで、以下のように msg.parts.count の値を変更してしまいます。
msg.payload *= 3;
msg.parts.count = 4;
return msg;
デプロイしてinjectノードをクリックし、表示内容を確認してみましょう。
出力される配列の要素数が4個に減ってしまっているのがわかります。うん、やはり parts オブジェクトを参照していましたね!
5個目の値はどうやら捨てられてしまったようです。想像ですが msg.parts.count が 4 なので、join ノードは長さが4の配列だと思い込み、4個目の値が入力されたところで配列を出力してしまったのでしょう。そこに5個目が届いても、もうそのIDは処理が終了していますし、index が0ではない値ですし、更に index が4個の上限を超えていますから、異常値と判断して出力しなかったのだと考えられます。
というわけで
split ノードで分割した場合、元の配列の情報は msg.parts オブジェクトに格納されており、join ノードではそれを参照して配列に戻しているようです。
function ノードなどに記載する自身のコードでは、msg.parts には不用意に触らないほうが良さそうですね。
おわりに
配列を操作して遊ぶだけで第二歩の投稿が終わってしまいました。
でもフロー型の記述らしいサンプルになった気がしますし、疑問がひとつ解消できたので、個人的にはちょっと満足していたりします。次は function ノードに関して です。
ではでは。