リンクコールノードによってNode-REDの使い勝手が大きく向上しました。でも、サブフローって前からありました。もういらないのかな?などと考えてしまいがちですが、大きな違いがありますよ!というのがこの記事の内容です。まずはリンクコールの便利な点とサブフローの注意点をあげて、そのあとに違いを語りたいと思います。
TLDR;
- リンクコール便利だよね
- サブフローならここに注意が必要
- リンクコールの呼び出しはシングルトン
- サブフローのテンプレートはクラス。フロー中のサブフローはインスタンス
- おまけ
リンクコールの利点
従来のリンクノードはGOTO文なので、別のフローを呼び出しても、呼び出したところに戻ることはできませんでした。戻るためのリンクノードを書いても1か所にしか飛べないので、処理の共通化ができませんでした。処理の共通化をするにはサブフローが以前から使えましたが、これが何かと使いづらいものでした。
そこで現れたのがリンクコールノードです。link callでlink inのつながったフローを呼び出すと、「link callへ返却」というモードにセットしたlink outノードで戻ってくることができます。サブフローと比べて以下のような使いやすさがあります。
- 常にフローのタブが表示されているのですぐに編集できる
- サブフロー:タブは普段閉じた状態
- 左のパレットをのダブルクリック、または、フロー中のサブフローを開き「サブフローのテンプレートを編集」を押下する
- サブフロー:タブは普段閉じた状態
- リンクコールノードを含むフローを読み込んでも、呼び出し先は変わらない
- サブフロー:同じサブフローや、それを含むフローを読みだすとつい間違って複製してしまい、別のサブフローになる
*「ノードを参照」押下。サブフローを読み込まないか、「置換」のチェックを入れる
- サブフロー:同じサブフローや、それを含むフローを読みだすとつい間違って複製してしまい、別のサブフローになる
- 入口を複数持てる(類似処理の一部共通化が容易)
- サブフロー:入口は一つなので処理の切り替えはmsgの値を用いないといけない
- スパゲティになりにくいとも言えるので我慢する
- サブフロー:入口は一つなので処理の切り替えはmsgの値を用いないといけない
- flow変数は実行時のタブのflow変数なので直感的
- サブフロー:flow変数は個々のサブフローごとのflow変数になる
特に2番目の複製はサブフローの開発中にやりがちだったので、精神的に楽になりました。
この記事では最後に書いたflow変数のほか、ノード固有のcontext変数について説明します。
リンクコール先の実体はただ一つ
それではリンクコールを使ってみましょう。
ファンクションノードでflow変数とcontext変数をインクリメント(+1)します。通常の処理のだと、flow変数はタブの中の変数で、context変数はノードの中の変数です。
以下のような処理をそれぞれ二つのファンクションノードに書いてみます。どちらも変数が違うだけでほとんど同じです。前半で変数がなければ初期値をセットして、後半でインクリメントした値をそれぞれの変数にセットするとともにmsg.payloadに出力しています。
let a = context.get("a");
if (!a) {
a = 0;
}
a++;
context.set("a", a);
msg.payload = "context:"+a;
return msg;
let a = flow.get("a");
if (!a) {
a = 0;
}
a++;
flow.set("a", a);
msg.payload = "flow:"+a;
return msg;
それぞれの入ったファンクションノードを以下のように並べます。
順にインジェクトノードを押すと、以下のようにflow変数は増えていきますが、context変数はそれぞれ1のままです。
ではこれをリンクコールで実現します。インジェクトノードでなくlink inノード、デバッグノードでなくlink outノードのものを作成します。今回は上記のフローと同じタブに置きます。
link outノードはそのままではlink callに戻れないので、モードを「link callノードへ返却」にします。
呼び出し側はインジェクトノードとデバッグノードの間にlink callノード入れたものを2セット用意します。link callノードは先ほどのlink inノードを呼び出すようにセットします。
この状態で追加したインジェクトノードを順にクリックすると、以下のようにflow変数は先ほどの続きで増加、context変数は1から順に増加します。
context変数はlink call用モジュールのファンクションノードが1つなので、それが増加しました。
このようにlink callで呼び出した場合は見た目の通り以下のような動作をします。
- flow変数は参照時(呼び出された先)のflow変数が用いられます(今回は呼び出し元と同じタブなので続きで増加しましたが、別のタブにある際に同じような動作を期待する場合は、global変数にしてください)
- context変数も参照時のファンクションノードの変数が用いられます
これはlink call の呼び出し先の実体はただ一つ、つまりシングルトンあるいはstatic関数と考えるとわかりやすいと思います。
サブフローのテンプレートはフローの中で実体化される
link call用フローと同じように、サブフローを作成します。
この状態で追加したインジェクトノードを順にクリックすると、以下のようにflow変数もcontext変数もそれぞれ1になります。
このように、サブフローはlink callと見た目は似ていますが、以下のような動作をします。
- flow変数は実行時の(実体化された)サブフロー内のflow変数が用いられます。フローに配置されたそれぞれのサブフローが独立しています。
- context変数も実行時の(実体化された)サブフロー内のファンクションノードが持つcontext変数が用いられます。フローに配置されたものそれぞれサブフローのファンクションノードが独立しています。
これはサブフローのテンプレートはクラスで、フロー内でサブフローが実体化されるインスタンスと考えるとわかりやすいと思います。
おまけ
flow変数やcontext変数と同じようなことが通信ノードでも生じます。以下のようにwebsocketノードとTCPノードをサブフローに入れて接続します。
すると呼び出された側はプロトコルによって接続数が変わります。
興味のある方はソースをのぞいてみると面白いでしょう。
今回のソース
[{"id":"7e93908f05afdc2e","type":"subflow","name":"websocketとtcp","info":"","category":"","in":[],"out":[],"env":[],"meta":{},"color":"#DDAA99"},{"id":"6a32d3897e828b69","type":"group","z":"7e93908f05afdc2e","name":"websocketとtcp","style":{"fill":"#bfdbef","label":true,"color":"#000000"},"nodes":["91b5c777f92a6c35","294d7c3a5330d436"],"x":114,"y":39,"w":312,"h":142},{"id":"91b5c777f92a6c35","type":"websocket out","z":"7e93908f05afdc2e","g":"6a32d3897e828b69","name":"","server":"","client":"c33b9dcf42bfa061","x":270,"y":80,"wires":[]},{"id":"294d7c3a5330d436","type":"tcp out","z":"7e93908f05afdc2e","g":"6a32d3897e828b69","name":"","host":"localhost","port":"3000","beserver":"client","base64":false,"end":false,"tls":"","x":230,"y":140,"wires":[]},{"id":"c33b9dcf42bfa061","type":"websocket-client","path":"ws://localhost:1880/ws/tes1","tls":"","wholemsg":"false","hb":"0","subprotocol":""}]