编写 Node-RED Function 节点

编写 Node-RED Function 节点

Function 节点可以运行 JavaScript 代码,处理经过它的消息。消息以名为 msg 的对象传入;按照约定,msg.payload 属性保存消息正文。其他节点可能向消息添加自己的属性,这些属性应在相应节点的文档中说明。

编写函数

Function 节点中输入的代码构成函数体。最简单的函数原样返回消息:

return msg;

如果函数返回 null,就不会继续传递消息,流程在此结束。函数必须返回消息对象;返回数字或字符串会导致错误。

返回的对象不必与传入的对象相同,也可以新建对象:

const newMsg = { payload: msg.payload.length };
return newMsg;

注意:新建消息对象会丢失收到的消息上的其他属性,可能破坏某些流程。例如 HTTP In/Response 流程要求端到端保留 msg.req 和 msg.res。通常应修改传入消息的属性,然后返回同一个消息对象。

可以使用 node.warn() 在侧栏显示警告,辅助调试:

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

更多内容见下文“记录日志”。

多个输出

函数编辑对话框允许修改输出数量。存在多个输出时,可以返回消息数组,把消息发往相应输出。

这样便能按条件选择输出。例如,将 topic 为 banana 的消息发往第二个输出,其他消息发往第一个输出:

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

下面的例子从第一个输出原样发送原消息,从第二个输出发送包含 payload 长度的消息:

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

处理任意数量的输出

node.outputCount 包含此 Function 节点配置的输出数量。因此,可以编写通用函数,适应编辑对话框中设置的不同输出数量。下面将收到的消息随机分配到一个输出:

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

之后只需在编辑对话框中修改输出数量,不必修改函数本身。

多条消息

在返回数组中再放入消息数组,就能从同一个输出发送多条消息。下游节点会按返回顺序逐条收到这些消息。

下面将 msg1、msg2、msg3 发往第一个输出,将 msg4 发往第二个输出:

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

下面将收到的 payload 拆成单词,为每个单词返回一条消息:

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

异步发送消息

如果函数必须先执行异步操作再发送消息,就不能在函数末尾直接返回该消息。应调用 node.send(),传入要发送的消息。它接受的消息排列方式与前面介绍的返回值相同。例如:

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

Function 节点会克隆传给 node.send 的每个消息对象,以免重复使用的对象被意外修改。Node-RED 1.0 之前,传入的第一条消息不会被克隆,其余消息会被克隆。

将第二个参数设为 false,可以要求运行时不要克隆传入的第一条消息。当消息含有无法克隆的内容,或需要降低发送消息的性能开销时,可以这样做:

node.send(msg,false);

完成消息处理

Function 节点异步处理消息时,运行时不会自动知道处理何时完成。应在适当时机调用 node.done(),让运行时正确追踪消息在系统中的处理情况:

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

启动时运行代码

从 1.1.0 起,Function 节点提供 On Start 标签页(1.3.0 之前名为 Setup)。这里的代码会在每次节点启动时运行,可用于建立函数所需的状态。

例如,初始化主函数会使用的本地上下文值:

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.
});

或者,在节点编辑对话框的 On Stop 标签页中编写清理代码。

记录日志

节点需要向控制台记录信息时,可以使用以下函数:

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

控制台输出的位置取决于操作系统和 Node-RED 的启动方式。从命令行启动时,日志就在该控制台;作为系统服务运行时,可能写入系统日志;使用 PM2 等应用运行时,日志由该应用自己的方式展示。在树莓派上,安装脚本会添加 node-red-log 命令,用来显示日志。

warn 和 error 消息也会发送到流程编辑器右侧的调试标签页。更细粒度的日志可以使用 node.trace() 和 node.debug();如果没有配置记录这些级别的日志器,就看不到它们。

处理错误

如果函数遇到应当终止当前流程的错误,就不应返回任何内容。要触发同一标签页中的 Catch 节点,应调用 node.error,并把原消息作为第二个参数:

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

保存数据

除了 msg 对象,函数还可以在上下文存储中保存数据。更多说明见 Node-RED 上下文文档。

Function 节点提供三个用于访问上下文的预定义变量:

  • context:节点本地上下文。
  • flow:流程作用域上下文。
  • global:全局作用域上下文。

以下示例使用 flow,同样适用于 context 和 global。这些预定义变量是 Function 节点的功能;自行创建节点时,请参阅创建节点指南。

访问上下文有同步和异步两种方式。内置上下文存储同时支持两种方式;某些存储仅支持异步访问,同步访问会抛出错误。

获取上下文值:

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

设置上下文值:

flow.set("count", 123);

下面统计函数运行次数:

// initialise the counter to 0 if it doesn't exist already
let 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;

获取或设置多个值

也可以一次获取或设置多个值:

let values = flow.get(["count", "colour", "temperature"]);
// values[0] is the 'count' value
// values[1] is the 'colour' value
// values[2] is the 'temperature' value
flow.set(["count", "colour", "temperature"], [123, "red", "12.5"]);

此时,任何缺失的值都会设为 null。

异步访问上下文

如果上下文存储要求异步访问,get 和 set 需要额外的回调参数:

// 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 起,可以配置多个上下文存储,例如同时使用 memory 和 file 存储。get/set 接受一个可选参数,指定使用哪一个存储:

// Get value - sync
let 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) { ... })

必要时,上面示例中的 storeName 可以使用变量。

全局上下文

Node-RED 启动时,可以向全局上下文预先填入对象。在主 settings.js 文件的 functionGlobalContext 属性中定义这些对象,也可由此加载额外模块。

添加状态

Function 节点与其他节点一样,可以显示自己的状态。调用 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 节点可以捕获这些状态更新。

加载额外模块

使用 functionGlobalContext

不能直接在 Function 节点中加载额外的 Node 模块。必须在 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

在 settings.js 中把 functionExternalModules 设为 true 后,Function 节点编辑对话框会提供模块列表,可以添加此节点应能使用的额外模块,并指定代码中引用该模块的变量名。

Function 节点的外部模块配置(官方截图)

部署节点时,这些模块会自动安装到 ~/.node-red/node_modules/。

处理超时

可以在 Setup 标签页为 Function 节点设置超时,单位为秒。此值规定运行时允许函数执行多久,超过后抛出错误。默认值为 0,表示不设置超时。

注意:超时功能仅适用于同步代码。

此功能从 Node-RED 5 开始提供。Function 节点可以内联调用其他流程,并在收到响应后继续执行。

如果需要复用工具流程,又不希望处理纯流程方式带来的状态管理,这会很有用。例如,要在 Function 节点中操作数据库,不必自行编写全部数据库代码:可以使用现有数据库节点建立流程,然后从函数直接调用该流程。

用 Link 节点创建工具流程:

使用 Link 节点建立数据库查询流程(官方示意图)

把 link out 节点的选项设为 return to calling link node。

使用 node.linkcall

Function 节点通过 node.linkcall 调用 Link 节点:

// Set a query for the sqlite node to use
msg.topic = 'select * from orders';
// Call the `database-query` link node and await a response
const result = await node.linkcall('database-query', msg);

// result.payload contains the result of the database query

node.linkcall(target, message, options)

  • target:要调用的 link in 节点的字符串标识,可使用节点 id 或 name。
  • 消息参数:传给该流程的消息对象。
  • options:可选对象,包含 timeout(调用超时,单位毫秒,默认 5000,即 5 秒)和 clone(发送前是否克隆消息,默认 true)。

API 参考

Function 节点中可以使用以下对象。

node

API 用途
node.id Function 节点的 ID
node.name Function 节点的名称
node.outputCount 配置的输出数量
node.log(..) 记录普通日志
node.warn(..) 记录警告日志
node.error(..) 记录错误日志
node.debug(..) 记录调试日志
node.trace(..) 记录跟踪日志
node.on(..) 注册事件处理器
node.status(..) 更新节点状态
node.send(..) 发送消息
node.done(..) 标记消息处理完成
node.linkcall(..) 调用 link in 节点并等待响应;5.0 新增

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

其他模块和函数

还可以使用以下模块和函数:

  • Buffer:Node.js 的 Buffer 模块。
  • console:Node.js 的 console 模块;记录日志时优先使用 node.log。
  • util:Node.js 的 util 模块。
  • setTimeout/clearTimeout:JavaScript 超时定时器函数。
  • setInterval/clearInterval:JavaScript 间隔定时器函数。

Function 节点停止或重新部署时,会自动清除所有尚未结束的超时和间隔定时器。


来源:Writing Functions,Node-RED 官方文档。版权归 OpenJS Foundation 及贡献者所有。本文翻译自原文,技术标识与示例保持原样。

文档仓库采用 Apache License 2.0。原始版权通知:Copyright 2016 JS Foundation and other contributors;Copyright 2013, 2016 IBM Corp。许可全文:Apache License 2.0。文档按许可条款提供,不附带明示或默示担保。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容