Functionノードの書き方

Functionノードは、受け取ったメッセージに対してJavaScriptコードを実行することができ、フローを継続するためにゼロ個以上のメッセージを返します。

メッセージはmsgと呼ばれる一つのオブジェクトとして渡されます。このオブジェクトは慣例により、メッセージ本体を含むmsg.payload プロパティを持っています。

独自のプロパティをメッセージに追加することも可能ですが、そのことをドキュメントに記載するべきです。

  • Functionの作成
  • 複数の出力に送る
  • 複数のメッセージを送る
  • メッセージの非同期送信
  • イベントのログ
  • エラーの処理
  • データの保存(context)
    • フローコンテクスト
    • グローバルコンテクスト
  • 状態(Status)の追加
  • その他のモジュールと関数

Functionの作成

Functionノードに入力されたコードは、関数の本体に相当します。一番単純な関数は、メッセージを単にそのまま返します:

return msg;

関数がnullを返すと, メッセージは何も渡されずにフローが終了します。

返されるメッセージオブジェクトは受け取ったオブジェクトと同一である必要はありません。全く新しいオブジェクトを作って返すこともできます。例えば:

var newMsg = { payload: msg.payload.length };
return newMsg;
注意: 新しいメッセージオブジェクトを作ると、受信した元のメッセージのあらゆるプロパティを失うことになります。これによりフローが壊れることがあり、例えば HTTP In/Response フローでは msg.reqmsg.res プロパティが最初から最後まで失われないようにする必要があります。一般論として、functionノードはどんな変更をプロパティに行っても、受け取ったメッセージオブジェクトを返すべきです。

複数の出力に送る

functionの編集ダイアログで出力の数を変更できます。2つ以上の出力がある場合、複数の出力にメッセージを送るために、複数のメッセージを含む一つの配列を返すことができます。

これにより、条件に応じて異なる出力にメッセージを送る関数を簡単に書くことができます。 例えば、以下の例は トピックが banana なら最初の出力ではなく2番目の出力に全てを送ります:

if (msg.topic === "banana") {
   return [ null, msg ];
} else {
   return [ msg, null ];
}

以下の例は、メッセージをそのまま最初の出力に渡し、そのメッセージのペイロードの長さを含むメッセージを2番目の出力に渡します:

var newMsg = { payload: msg.payload.length };
return [msg, newMsg];

複数のメッセージを送る

複数メッセージの配列を含む一つの配列を返すことで、一つの出力に複数のメッセージを返すことができます。一つの出力に複数のメッセージを返すと、後続のノードは返された配列の要素の順番にメッセージを一つずつ受け取ります。

以下の例では、msg1msg2msg3は最初の出力に送られます。msg4は2番目の出力に送られます。

var msg1 = { payload:"出力1への最初の出力" };
var msg2 = { payload:"出力1への二番目の出力" };
var msg3 = { payload:"出力1への三番目の出力" };
var msg4 = { payload:"出力2への唯一のメッセージ" };
return [ [ msg1, msg2, msg3 ], msg4 ];

以下の例は、受信したペイロードを個別の単語に分け、それぞれの単語を順次送る一つのメッセージを返します。

var outputMsgs = [];
var words = msg.payload.split(" ");
for (var w in words) {
    outputMsgs.push({payload:words[w]});
}
return [ outputMsgs ];

メッセージの非同期送信

メッセージを送信する前に何らかの非同期処理を行う必要がある場合、関数の最後で(非同期処理の結果の)メッセージを返すことはできません。

その場合、送信したいメッセージ(複数可)を node.send() 関数に渡して用いる必要があります。例えば:

doSomeAsyncWork(msg, function(result) {
    node.send({payload:result});
});
return;

関数の中で非同期のコールバックを用いる場合、フローが再デプロイされる際には、処理中のリクエストや使用中のコネクションなどを後片付けする必要があります。この後片付けは、 closeイベントハンドラを追加して行うことができます。

node.on('close', function() {
    // コネクションの切断など、全ての非同期コードの後片付けをここで行う
});

イベントのログ

ノードが何かをコンソールにログ出力する必要がある場合は、以下の関数のどれかを使うことができます:

node.log("何かが起きました");
node.warn("知っておくべき何かが起きました");
node.error("なんてこった、何か良くないことが起きました");

warnerror メッセージはフローエディタのdebugタブにも送られます。

エラーの処理

実行中のフローを停止させるべきエラーに遭遇した場合、関数は何もreturnするべきではありません。 同じタブのCatchノードをトリガーするために、元のメッセージを第二引数として node.error を呼ぶべきです:

node.error("エラー発生", msg);

データの保存

msgオブジェクトとは違って、functionノードは複数回の呼び出しの間で、自分のcontextオブジェクトの中にデータを保持しておくことができます。

以下の例は関数が何回実行されているか、その回数を保持します:

// もしまだカウンタが存在していなければ0に初期化
var count = context.get('count')||0;
count += 1;
// 値を書き戻して保存
context.set('count',count);
// 送信するメッセージの一部にカウントを含める
msg.count = count;

デフォルトではcontextのデータはNode-REDを再起動すると保持されません

注意: Node-REDのv0.13より以前のバージョンでは、contextを使う方法として、直接アクセスするやり方がドキュメントに記述されていました:
var count = context.count;
この方法はまだサポートされていますが非推奨であり、 context.get/context.set を使う方法が推奨されています。これは、将来のリリースでcontextデータが永続化できるようになることを見込んでのものです。
フローコンテクスト

Node-RED 0.13とそれ以降のバージョンでは、contextオブジェクトが一つのノードだけからアクセスできるのと同様に、functionノード以外のノードも含む、同一タブ上の全てのノードに共有されるフローレベルのコンテクストもあります。フローコンテクストはflowオブジェクト経由でアクセスされます:

var count = flow.get('count')||0;
グローバルコンテクスト

全てのノードに共有されてアクセスできるグローバルコンテクストも使うことができます。例えば、キャンバスのどこからでもグローバルに使える変数 foo を作るには:

global.set("foo","bar");  // これは他のノードから利用可能です

とし、.getで読み取ることができます。

var myfoo = global.get("foo");  // これは"bar"になります

グローバルコンテクストはNode-REDの起動時にオブジェクトと共に前もって生成しておくこともできます。メインのsettings.jsの中のfunctionGlobalContextプロパティの中で定義することでそのように動作します。

例えば、ビルトインのosモジュールを全てのfunctionノードから利用可能にするには、以下のようにします:

functionGlobalContext: {
    osModule:require('os')
}

この時点で、osモジュールはfunctionノードの中でglobal.get('osModule')のように参照できるようになります。

“require”が必要な外部モジュールは全てnpmでユーザディレクトリに手動インストールしなければなりません。

cd ~/.node-red
npm i name_of_3rd_party_module_to_be_required
注意: Node-RED v0.13より前のバージョンでは、グローバルコンテクストを使う方法は、以下のようにcontextのサブプロパティとしてアクセスするとドキュメントに記述されていました:
context.global.foo = "bar";
var osModule = context.global.osModule;
この方法はまだサポートされていますが非推奨であり、global.get/global.set を使う方法が推奨されています。これは、将来のリリースでcontextデータが永続化できるようになることを見込んでのものです。

状態(Status)の追加

他のノードが行えるのと同じように、functionノードも状態表示の修飾を提供することができます。状態をセットするには、node.status関数を呼びます。例えば

node.status({fill:"red",shape:"ring",text:"切断されています"});
node.status({fill:"green",shape:"dot",text:"接続されています"});
node.status({text:"文字だけの状態"});
node.status({});   // 状態をクリアします

指定可能なパラメータの詳細についてはNode Status documentationを参照してください。

状態のどんな変化でも、Statusノード(Node-RED v0.12以降で利用可能)でキャッチできます。

その他のモジュールと関数

functionノードは以下のモジュールと関数を利用可能です:

  • Buffer - Node.jsのBufferモジュール
  • console - Node.jsのconsoleモジュール(ログ出力にはnode.logを利用する方が良いです。)
  • util - Node.jsのutilモジュール
  • setTimeout/clearTimeout - javascriptのtimeout関数
  • setInterval/clearInterval - javascriptのinterval関数

注意: functionノードは、停止もしくは再デプロイ時に、全ての未完了のタイムアウトやインターバルタイマーを自動的にクリアします。