• V
 

Functionノードの書き方

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

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

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

Functionの作成

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

return msg;

Functionノードがnullを返すと、メッセージは渡されずにフローが終了します。

Functionノードは必ずmsgオブジェクトを返します。 数値や文字列を返すとエラーが発生します。

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

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

デバッグを助けるため、 node.warn() はサイドバーに警告を表示します。例えば:

node.warn("my var xyz = " + xyz);

詳細情報は以下のログに関するセクションを確認してください。

複数の出力に送る

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

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

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

以下の例は、入力されたメッセージをそのまま最初の出力に渡し、 そのメッセージのpayloadの長さを含むメッセージを2番目の出力に渡します。:

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

任意の数の出力を処理する

Node-RED 1.3以降

node.outputCount には、Functionノードに設定された出力の数が含まれます。

これにより、編集ダイアログにセットされた出力数を変数にハンドルして関数を作成することができます。

たとえば、受信メッセージを出力の間でランダムに拡散したい場合は、次のように書きます:

// 出力数と同じ長さの配列を生成
const messages = new Array(node.outputCount)
// メッセージ送信する乱数出力を選択
const chosenOutputIndex = Math.floor(Math.random() * node.outputCount);
// 選択した出力だけにメッセージを送信
messages[chosenOutputIndex] = msg;
// 選択された出力を含む配列を返す
return messages;

関数自体を変更することなく、編集ダイアログの設定のみで出力数を構成できるようになりました。

複数のメッセージを送る

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

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

var msg1 = { payload:"first out of output 1" };
var msg2 = { payload:"second out of output 1" };
var msg3 = { payload:"third out of output 1" };
var msg4 = { payload:"only message from output 2" };
return [ [ msg1, msg2, msg3 ], msg4 ];

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

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

メッセージの非同期送信

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

その場合、送信したいメッセージ(複数可)をnode.send()関数に渡して用いる必要があります。 前のセクションで記述したように、 返却するメッセージは同じ配置にします。

以下がその例です。:

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

Functionノードはnode.sendに渡されたメッセージオブジェクトを全てクローンします。 これは関数内で再利用されるメッセージオブジェクトが意図しない変更がされないようにするためです。 Node-RED 1.0以前では、Functionノードはnode.sendに渡された最初のメッセージはクローンされませんが、 残りはクローンされます。

Functionノードはnode.sendに渡された第一引数をクローンしないことを、 この関数に第二引数としてfalseを受け渡すことによって、実行環境に要求できます。 これは、メッセージにクローンできないものが含まれている場合、 またはパフォーマンス上の理由でメッセージ送信のオーバーヘッドを最小限に抑える場合におこなわれます。

node.send(msg,false);

終了時メッセージ

Node-RED 1.0以降

Functionノードはメッセージについて非同期処理をおこなう場合、 メッセージ処理が終了したとき実行環境は自動的に検知します。

これを助けるため、Functionノードでは適切なタイミングでnode.done()を呼び出すことが推奨されます。 これはランタイムにシステムを介してメッセージを適切に追跡できるようにします。

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

起動時にコードを実行する

Node-RED 1.1.0以降

1.1.0リリースによって、 Functionノードはノードが起動されたときに実行されるコードを提供する初期化処理タブを提供しています。 これはFunctionノードが必要とする状態にセットアップするために利用されます。

例えば、 メインの関数が利用するローカルコンテキストに値を初期化します:

if (context.get("counter") === undefined) {
    context.set("counter", 0)
}

初期化処理関数は、 メインの関数がメッセージを処理し始める前に非同期ジョブを完了させておく必要がある場合にPromiseを返却できます。 初期化処理関数が完了する前に到達した全てのメッセージはキューに蓄積され、準備ができたら処理されます。

片付け

Functionノードの中で非同期のコールバックを用いる場合、 フローが再デプロイされる際に処理中のリクエストや使用中のコネクションなどを後片付けする必要があります。 この後片付けは、2種類の方法で実施できます。

closeイベントハンドラを追加します:

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

もしくは、Node-RED 1.1.0から、 ノードの編集ダイアログに終了処理タブにコードを追加できるようになりました。

イベントのログ

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

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

コンソール出力がどこに表示されるのかはどのOSを利用しているかとどのようにNode-REDを起動したかに依ります。 コマンドラインを使って起動した場合 - ログはコンソールに出力されます。 システムサービスとして実行した場合、システムログに出力されます。PM2のようにアプリの元で実行した場合、独自のログ表示になります。ラズベリーパイ上のインストールスクリプトは、ログを表示させるnode-red-logコマンドを追加します。

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

より詳細なログには、node.trace()およびnode.debug()が利用できます。 これらのレベルを出力するようにログが設定されていない場合、ログには出力されません。

エラーの処理

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

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

データの保存

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

Node-REDのコンテキストについての詳細な情報はこちらで確認することができます。

Functionノードの場合、 コンテキストにアクセスすることができる3つの定義済み変数が存在します。:

  • context - ノードスコープのローカルなコンテキスト
  • flow - フロースコープのコンテキスト
  • global - グローバルスコープのコンテキスト

以下の例ではflowコンテキストを利用しますが、 contextおよびglobalでも同様に利用できます。

Note : これらの定義済み変数はFunctionノードの特徴です。 ノードを自作する場合は、コンテキストへのアクセス方法はノード作成ガイドを参照してください。

コンテキストへのアクセスには、同期または非同期の2種類のモードがあります。 既存のコンテキストストアは両方のモードを提供しています。いくつかのストアは非同期アクセスのみ提供しており、 同期してアクセスがおこなわれるとエラーを発生させます。

コンテキストから値を取得するには以下のようにします。:

var myCount = flow.get("count");

値を設定するには以下のようにします。:

flow.set("count", 123);

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

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

複数の値の取得/設定

Node-RED 0.19以降、複数の値の取得または設定を1回でできるようになっています。:

// Node-RED 0.19以降
var values = flow.get(["count", "colour", "temperature"]);
// values[0]には'count'の値が格納される
// values[1]には'colour'の値が格納される
// values[2]には'temprature'の値が格納される
// Node-RED 0.19以降
flow.set(["count", "colour", "temperature"], [123, "red", "12.5"]);

この方法では、値が見つからない場合にはnullが設定されます。

コンテキストへの非同期なアクセス

コンテキストストアでのデータ保管に非同期なアクセスが必要な場合、 getおよびset関数はコールバック引数が必須です。

// 単一の値を取得
flow.get("count", function(err, myCount) { ... });

// 複数の値を取得
flow.get(["count", "colour"], function(err, count, colour) { ... })

// 単一の値を設定
flow.set("count", 123, function(err) { ... })

// 複数の値を設定
flow.set(["count", "colour", [123, "red"], function(err) { ... })

コールバック関数に渡される第1引数errは、 コンテキストにアクセスする際にエラーが発生する場合のみセットします。

先述のカウンタの例を、非同期におこなうと以下のようになります。:

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

複数のコンテキストストア

0.19版では、複数のコンテキストストアに値を格納できます。 例として、memoryおよびfileといったストアの両方を利用することができます。

get/set関数は、利用するストアを特定するため、 任意の引数を利用することができます。

// 値の同期取得
var myCount = flow.get("count", storeName);

// 値の非同期取得
flow.get("count", storeName, function(err, myCount) { ... });

// 値の同期設定
flow.set("count", 123, storeName);

// 値の非同期設定
flow.set("count", 123, storeName, function(err) { ... })

globalコンテキスト

globalコンテキストは、Node-RED起動時にオブジェクトを予め格納しておくことができます。 オブジェクトは、settings.jsファイルのfunctionGlobalContextプロパティで 定義することができます。

このテクニックはFunctionノード内で追加モジュールのロードに 利用することができます。

ステータスの追加

他のノードが行えるのと同じように、Functionノードもステータス表示を装飾することができます。 ステータスをセットするには、node.status関数を呼びます。 例えば

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

指定可能なパラメータの詳細については ノードのステータスを参照してください。

ステータスのどんな変化でも、Statusノードでキャッチできます。

追加モジュールのロード

functionGlobalContext オプションの利用

Functionノードに追加モジュールを直接ロードすることはできません。 settings.jsファイルでロードし、 functionGlobalContextプロパティに追加される必要があります。

例えば、os組込みモジュールはsettings.jsファイルに以下の設定を追加することによって、 すべてのFunctionノードで利用可能になります。

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

この時点で、モジュールはFunctionノード内で以下のように参照できます。 global.get('osModule')

設定ファイルでロードされたモジュールは、設定ファイルと同じディレクトリにインストールされる必要があります。 デフォルトのユーザディレクトリ(~/.node-red)を利用しているだろうほとんどのユーザは以下のようにインストールを実行します。:

cd ~/.node-red
npm install name_of_3rd_party_module

functionExternalModules オプションを使う

Node-RED 1.3.0以降

settings.js ファイルの functionExternalModulestrue にセットすることで、 Functionノードの編集ダイアログには モジュールを追加できるリストが表示されます。 また、ノードのコード内でモジュールを参照するために使用する変数を指定します。

追加するモジュールは、ノードがデプロイされる時に ~/.node-red/externalModules/ に自動的にインストールされます。

Handling a Timeout

Since Node-RED 3.1.0

It is possible to set a timeout for the function node on the Setup tab. This value, in seconds, is how long the runtime will allow the Function node to run for before raising an error. If set to 0, the default, no timeout is applied.


APIリファレンス

Functionノード内では以下のオブジェクトが利用できます。

node

context

  • context.get(..) : ノードスコープコンテキストからプロパティ値を取得する
  • context.set(..) : ノードスコープコンテキストにプロパティ値を設定する
  • context.keys(..) : すべてのノードスコープコンテキストのプロパティキーの一覧を取得する
  • context.flow : flowと同義です
  • context.global : globalと同義です

flow

  • flow.get(..) : フロースコープコンテキストからプロパティ値を取得する
  • flow.set(..) : フロースコープコンテキストにプロパティ値を設定する
  • flow.keys(..) : すべてのフロースコープコンテキストのプロパティキーの一覧を取得する

global

  • global.get(..) : グローバルスコープコンテキストからプロパティ値を取得する
  • global.set(..) : グローバルスコープコンテキストにプロパティ値を設定する
  • global.keys(..) : すべてのグローバルスコープコンテキストのプロパティキーの一覧を取得する

RED

  • RED.util.cloneMessage(..) : メッセージオブジェクトを再利用できるように安全にクローンします

env

  • env.get(..) : 環境変数を取得する

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

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

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

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