编写函数

函数节点可以让我们使用JavaScript对传入的消息进行处理,然后返回多条消息,以使流程继续执行。

传入的消息一般以对象形式表示,其名称为msg,通常都会包含一个msg.payload属性,用来保存消息的主体内容。

其它类型的节点可能会在消息中附加其他专有属性,并且会在文档中加以详细说明。

编写函数

输入函数节点的代码实际是JavaScript函数的主体部分,最简单的功能就是直接将收到的消息返回:

return msg;

如果函数返回null,那么就意味着没有消息传出,流程将被终止。

返回的消息对象不必于传入的对象保持一致,函数可以构建一个完全不同的新返回对象,比如:

var newMsg = { payload: msg.payload.length };
return newMsg;
注意: 构造一个全新的消息对象,有可能会丢失所接收消息的所有属性,并且造成某些流程中断。比如对于使用HTTP In/Response的流程而言,要求msg.reqmsg.res在端到端的传输过程中一致保留。通常情况下,函数节点应该返回它们所接收到的完整消息对象,只改变其中的某些特性。

多输出端口

函数编辑器允许对输出端口数量进行修改。如果需要多个输出,那么可以通过返回一个消息数组,实现多端口传输功能。

这使得根据特定条件向不同端口发送消息成为可能。比如,以下函数中,将会把主题为banana的消息发往第二个端口:

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

以下例子则会将原始消息发往第一个端口,而把包含载荷长度的消息发往第二个端口:

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

多条消息

函数可以在结果数组中,以消息数组的方式输出多条消息。当多条消息被发往一个输出端口时,后续的节点将按照消息的排列顺序依次进行接收。

下例中的msg1msg2msg3将发向第一个端口,msg4则发往第二个输出端口。

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 ];

以下的代码将接收到的载荷分解为词,然后按词逐条发送。

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("Something happened");
node.warn("Something happened you should know about");
node.error("Oh no, something bad happened");

warnerror等信息也会被发往流程编辑器中的调试面板。

处理错误

函数执行过程中,如果遇到有可能造成流程停止的错误时,将不会返回任何结果。为了能够触发同一个标签面板中的Catch节点,函数应该调用node.error,并且将原始消息作为其第二个参数:

node.error("hit an error", msg);

保存数据

msg对象外,函数还可以将数据保存在context对象中。

以下实例实现了函数执行次数的保存功能:

// 如果计数器不存在则将其初始化为0
var count = context.get('count')||0;
count += 1;
// 保存数据
context.set('count',count);
// 将其作为输出消息的一部分
msg.count = count;

默认情况下,当Node-RED重启时,环境数据并不能被持久保存。

注意: 在Node-RED v0.13以前版本的文档中,是直接通过context访问环境数据的:
var count = context.count;
虽然这种方法目前仍被支持,但不赞成继续使用。为保证在今后的版本中,数据持久化相关功能仍然有效,建议使用context.get/context.set函数。
流程环境

在Node-RED 0.13以上版本中,只有context对象是作用于节点本地范围的,另有一个流程级别的环境对象可以实现同面板所有节点间数据分享,而不是只对Function节点,它是通过flow对象来访问的:

var count = flow.get('count')||0;
全局环境

There is also a global context available that is shared by, and accessible to all nodes. For example to make the variable foo available globally across the canvas: 还有一个全局环境对象,可以被所有节点访问。比如下面的变量foo能被全局访问到:

global.set("foo","bar");  // this is now available to other nodes

并且通过.get读取其保存的数值。

var myfoo = global.get("foo");  // this should now be "bar"

也可以在Node-RED启动时,用对象对全局环境进行预填充,这项功能是通过主settings.js文件中的functionGlobalContext项来设置的。

比如,为使所有函数都能访问预置的os模块:

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

此时,该模块就能以global.get('osModule')方式被引用了。

如果需要引用外部模块,必须首先利用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替代它们,因为以这种方式所保存的数据会在未来版本中一直可用。

增加状态

函数节点也可以像其他节点那样,为自身提供状态装饰功能。如果要设置状态,需调用node.status函数。例如

node.status({fill:"red",shape:"ring",text:"disconnected"});
node.status({fill:"green",shape:"dot",text:"connected"});
node.status({text:"Just text status"});
node.status({});   // to clear the status

详细参数说明参见节点状态文档

任何状态变化都能被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函数

注意: 无论何时,当流程停止或重新部署时,函数节点都会自动清除未完成的超时或间隔计时对象。