编写函数

函数节点允许对流经它的消息运行 JavaScript 代码。

消息以一个名为 msg 的对象传入。按照惯例,它将具有一个包含消息正文的 msg.payload 属性。

其他节点可能会将自己的属性附加到消息中,这些属性应在其文档中进行说明。

编写函数

在函数节点中输入的代码代表函数的 主体。最简单的函数只是按原样返回消息

return msg;

如果函数返回 null,则不会传递任何消息,并且流程结束。

函数必须始终返回一个 msg 对象。返回数字或字符串将导致错误。

返回的消息对象不必与传入的对象相同;函数可以在返回之前构造一个全新的对象。例如

var newMsg = { payload: msg.payload.length };
return newMsg;
注意:构造新的消息对象将丢失接收到的消息的任何消息属性。这将破坏某些流程,例如 HTTP 输入/响应流程要求 msg.reqmsg.res 属性端到端保留。通常,函数节点 应该 返回它们接收到的消息对象,并在其属性上进行任何更改。

使用 node.warn() 在侧边栏中显示警告以帮助您调试。例如

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

有关更多详细信息,请参阅下面的日志记录部分。

多个输出

函数编辑对话框允许更改输出的数量。如果有一个以上的输出,函数可以返回一个消息数组以发送到输出。

这使得编写一个根据某些条件将消息发送到不同输出的函数变得容易。例如,此函数会将主题为 banana 的任何内容发送到第二个输出而不是第一个输出

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

以下示例将原始消息按原样传递到第一个输出,并将包含有效负载长度的消息传递到第二个输出

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

处理任意数量的输出

自 Node-RED 1.3 起

node.outputCount 包含为函数节点配置的输出数量。

这使得编写可以处理从编辑对话框设置的可变数量输出的通用函数成为可能。

例如,如果您希望将传入消息随机分散到各个输出之间,您可以

// Create an array same length as there are outputs
const messages = new Array(node.outputCount)
// Choose random output number to send the message to
const chosenOutputIndex = Math.floor(Math.random() * node.outputCount);
// Send the message only to chosen output
messages[chosenOutputIndex] = msg;
// Return the array containing chosen output
return messages;

您现在可以仅通过编辑对话框配置输出数量,而无需更改函数本身。

多条消息

函数可以通过在返回的数组中返回一个消息数组,在单个输出上返回多条消息。当为某个输出返回多条消息时,后续节点将按返回的顺序一次接收一条消息。

在以下示例中,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) {
    msg.payload = result;
    node.send(msg);
});
return;

函数节点将克隆您传递给 node.send 的每个消息对象,以确保函数中重复使用的消息对象不会被意外修改。在 Node-RED 1.0 之前,函数节点不会克隆传递给 node.send第一条 消息,但会克隆其余消息。

函数可以通过将 false 作为第二个参数传递给函数,请求运行时 不克隆 传递给 node.send 的第一条消息。如果消息包含无法克隆的内容,或者出于性能原因以最小化发送消息的开销,则会这样做

node.send(msg,false);

以消息结束

自 Node-RED 1.0 起

如果函数节点使用消息执行异步工作,则运行时不会自动知道何时完成处理消息。

为了帮助它,函数节点应该在适当的时候调用 node.done()。这将允许运行时正确地跟踪系统中的消息。

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

在启动时运行代码

自 Node-RED 1.1.0 起

随着 1.1.0 版本的发布,函数节点提供了一个 启动时 选项卡(1.3.0 之前标记为 设置),您可以在其中提供节点启动时运行的代码。这可以用于设置函数节点所需的任何状态。

例如,它可以在主函数将使用的本地上下文中初始化值

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

如果启动函数需要完成异步工作才能使主函数开始处理消息,则它可以返回一个 Promise。在启动函数完成之前到达的任何消息都将排队,并在准备好时进行处理。

清理

如果您在函数中使用异步回调代码,那么每当流程重新部署时,您可能需要清理任何未完成的请求,或关闭任何连接。您可以通过两种不同的方式进行。

通过添加 close 事件处理程序

node.on('close', function() {
    // tidy up any async code here - shutdown connections and so on.
});

或者,自 Node-RED 1.1.0 起,您可以将代码添加到节点编辑对话框中的 停止时 选项卡(以前标记为 关闭)。

日志事件

如果节点需要将某些内容记录到控制台,它可以使用以下函数之一

node.log("Something happened");
node.warn("Something happened you should know about");
node.error("Oh no, something bad happened");

控制台输出出现的位置将取决于您的操作系统以及您如何启动 Node-RED。如果您使用命令行启动 - 那就是日志将出现的控制台。如果您作为系统服务运行,则它可能会出现在系统日志中。如果您在 PM2 等应用程序下运行,它将有自己的显示日志的方式。在 Raspberry Pi 上,安装脚本添加了一个 node-red-log 命令,它将显示日志。

warnerror 消息也会发送到流程编辑器右侧的调试选项卡。

为了更细粒度的日志记录,node.trace()node.debug() 也可用。如果没有配置日志记录器来捕获这些级别,则它们将不可见。

错误处理

如果函数遇到应停止当前流程的错误,则它不应返回任何内容。要在同一选项卡上触发 Catch 节点,函数应调用 node.error 并将原始消息作为第二个参数

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

存储数据

除了 msg 对象之外,函数还可以将数据存储在上下文存储中。

有关 Node-RED 中上下文的更多信息,请参见此处

在函数节点中有三个预定义变量可用于访问上下文

  • context - 节点的本地上下文
  • flow - 流程范围上下文
  • global - 全局范围上下文

以下示例使用 flow 上下文,但也同样适用于 contextglobal

注意:这些预定义变量是函数节点的特性。如果您正在创建自定义节点,请查看 创建节点指南 以了解如何访问上下文。

访问上下文有两种模式;同步或异步。内置上下文存储提供两种模式。某些存储可能只提供异步访问,如果它们被同步访问,将抛出错误。

从上下文获取值

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

设置值

flow.set("count", 123);

以下示例维护函数运行的次数计数

// initialise the counter to 0 if it doesn't exist already
var count = context.get('count')||0;
count += 1;
// store the value back
context.set('count',count);
// make it part of the outgoing msg object
msg.count = count;
return msg;

获取/设置多个值

自 Node-RED 0.19 起,还可以一次性获取或设置多个值

// Node-RED 0.19 or later
var values = flow.get(["count", "colour", "temperature"]);
// values[0] is the 'count' value
// values[1] is the 'colour' value
// values[2] is the 'temperature' value
// Node-RED 0.19 or later
flow.set(["count", "colour", "temperature"], [123, "red", "12.5"]);

在这种情况下,任何缺失的值都设置为 null

异步上下文访问

如果上下文存储需要异步访问,getset 函数需要一个额外的回调参数。

// Get single value
flow.get("count", function(err, myCount) { ... });

// Get multiple values
flow.get(["count", "colour"], function(err, count, colour) { ... })

// Set single value
flow.set("count", 123, function(err) { ... })

// Set multiple values
flow.set(["count", "colour"], [123, "red"], function(err) { ... })

传递给回调的第一个参数 err 仅在访问上下文时发生错误时设置。

计数示例的异步版本变为

context.get('count', function(err, count) {
    if (err) {
        node.error(err, msg);
    } else {
        // initialise the counter to 0 if it doesn't exist already
        count = count || 0;
        count += 1;
        // store the value back
        context.set('count',count, function(err) {
            if (err) {
                node.error(err, msg);
            } else {
                // make it part of the outgoing msg object
                msg.count = count;
                // send the message
                node.send(msg);
            }
        });
    }
});

多个上下文存储

在 0.19 版本中,可以配置多个上下文存储。例如,可以使用 memoryfile 存储。

get/set 上下文函数接受一个可选参数来标识要使用的存储。

// Get value - sync
var myCount = flow.get("count", storeName);

// Get value - async
flow.get("count", storeName, function(err, myCount) { ... });

// Set value - sync
flow.set("count", 123, storeName);

// Set value - async
flow.set("count", 123, storeName, function(err) { ... })

全局上下文

Node-RED 启动时,全局上下文可以预先填充对象。这在主 settings.js 文件中的 functionGlobalContext 属性下定义。

这可以用于在函数节点中加载额外的模块

添加状态

函数节点也可以像其他节点一样提供自己的状态装饰。要设置状态,请调用 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

有关接受参数的详细信息,请参阅节点状态文档

任何状态更新也可以被状态节点捕获。

加载额外模块

使用 functionGlobalContext 选项

不能直接在函数节点中加载额外的节点模块。它们必须在您的 settings.js 文件中加载并添加到 functionGlobalContext 属性中。

例如,通过将以下内容添加到您的 settings.js 文件中,内置的 os 模块可以供所有函数使用。

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

此时,模块可以在函数中作为 global.get('osModule') 引用。

从设置文件加载的模块必须安装在与设置文件相同的目录中。对于大多数用户来说,这将是默认的用户目录 - ~/.node-red

cd ~/.node-red
npm install name_of_3rd_party_module

使用 functionExternalModules 选项

自 Node-RED 1.3.0 起

通过在 settings.js 文件中将 functionExternalModules 设置为 true,函数节点的编辑对话框将提供一个列表,您可以在其中添加应可用于节点的额外模块。您还可以指定将用于在节点代码中引用模块的变量。

模块在部署节点时会自动安装在 ~/.node-red/node_modules/ 下。

处理超时

自 Node-RED 3.1.0 起

可以在“设置”选项卡上为函数节点设置超时。此值以秒为单位,是运行时允许函数节点运行的时间,超过该时间将引发错误。如果设置为 0(默认值),则不应用超时。


API 参考

以下对象在函数节点中可用。

node

上下文

  • 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(..) :获取环境变量

其他模块和函数

函数节点还提供以下模块和函数

  • Buffer - Node.js Buffer 模块
  • console - Node.js console 模块(node.log 是首选的日志记录方法)
  • util - Node.js util 模块
  • setTimeout/clearTimeout - javascript 超时函数。
  • setInterval/clearInterval - javascript 间隔函数。

注意:函数节点在停止或重新部署时会自动清除任何未完成的超时或间隔计时器。