The Function node allows JavaScript code to be run against the messages that are passed through it.
The message is passed in as an object called msg
. By convention, it will have a msg.payload
property containing the body of the message. The other nodes may attach their own properties to the message object.
Writing a Function
The code entered into the Function node represents the body of the function.
The function below simply returns the message exactly as-is:
return msg;
If the function returns null
, then no message is passed on and the flow ends.
The function must always return a msg
object.
Returning a number or string will result in an error.
The returned message object does not need to be the same object as was passed in; the function can construct a completely new object before returning it. For example:
var newMsg = { payload: msg.payload.length }; return newMsg;
Constructing a new message object will lose any message properties of the received message. This will break some flows, for example, the HTTP In/Response flow requires the msg.req
and msg.res
properties to be preserved end-to-end. In general, function nodes should return the message object they were passed having made any changes to its properties.
Use node.warn()
to show warnings in the sidebar to help you debug. For example:
node.warn("my var xyz = " + xyz);
See the logging section below for more details.
Multiple Outputs
The function edit dialog allows the number of outputs to be changed. If there is more than one output, an array of messages can be returned by the function to send to the outputs.
This makes it easy to write a function that sends the message to different outputs depending on some conditions. For example, this function would send anything on the topic banana
to the second output rather than the first:
if (msg.topic === "banana") { return [ null, msg ]; } else { return [ msg, null ]; }
The following example passes the original message as-is on the first output and a message containing the payload length is passed to the second output:
var newMsg = { payload: msg.payload.length }; return [msg, newMsg];
Multiple Messages
A function can return multiple messages on the output by returning an array of messages within the returned array. When multiple messages are returned for an output, subsequent nodes will receive the messages one at a time in the order they were returned.
In the following example, msg1
, msg2
, msg3
will be sent to the first output. msg4
will be sent to the second output.
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 ];
The following example splits the received payload into individual words and returns a message for each of the words.
var outputMsgs = []; var words = msg.payload.split(" "); for (var w in words) { outputMsgs.push({payload:words[w]}); } return [ outputMsgs ];
Sending messages asynchronously
If the function needs to perform an asynchronous action before sending a message it cannot return the message at the end of the function.
Instead, it must make use of the node.send()
function, passing in the message(s) to be sent. It takes the same arrangement of messages as that can be returned, as described in the previous sections.
For example:
doSomeAsyncWork(msg, function(result) { msg.payload = result; node.send(msg); }); return;
The Function node will clone every message object you pass to node.send
to ensure there is no unintended modification of message objects that get reused in the function.
The Function can request the runtime to not clone the first message passed to node.send
by passing in false
as a second argument to the function. It would do this if the message contains something that is not otherwise cloneable, or for performance reasons to minimize the overhead of sending messages:
node.send(msg,false);
Finishing with a message
If a Function node does asynchronous work with a message, the runtime will not automatically know when it has finished handling the message.
To help it do so, the Function node should call node.done()
at the appropriate time. This will allow the runtime to properly track messages through the system.
doSomeAsyncWork(msg, function(result) { msg.payload = result; node.send(msg); node.done(); }); return;
Running code on start
The Function node provides a Setup
tab where you can provide code that will run whenever the node is started. This can be used to set up any state the Function node requires.
For example, it can initialize values in a local context that the main Function will use:
if (context.get("counter") === undefined) { context.set("counter", 0) }
The Setup function can return a Promise if it needs to complete asynchronous work before the main Function can start processing messages. Any messages that arrive before the Setup function has completed will be queued up, and handled when it is ready.
Tidying up
If you do use asynchronous callback code in your functions then you may need to tidy up any outstanding requests, or close any connections, whenever the flow gets re-deployed. You can do this in two different ways.
Either by adding a close
event handler:
node.on('close', function() { // tidy up any async code here - shutdown connections and so on. });
Logging events
If a node needs to log something to the console, it can use one of the following functions:
node.log("Something happened"); node.warn("Something happened you should know about"); node.error("Oh no, something bad happened");
The warn
and error
messages also get sent to the debug tab on the right side of the Experience Designer.
For finer-grained logging, node.trace()
and node.debug()
are also available. If there is no logger configured to capture those levels, they will not be seen.
Handling errors
If the function encounters an error that should halt the current flow, it should return nothing. To trigger a Catch node on the same tab, the function should call node.error
with the original message as a second argument:
node.error("hit an error", msg);
Storing data
Aside from the msg
object, the function can also store data in the context store.
In the Function node there are three predefined variables that can be used to access context:
context
- the node’s local contextflow
- the flow scope contextglobal
- the global scope context
The following examples use flow
context but apply equally well to context
and global
.
There are two modes for accessing context; either synchronous or asynchronous. The built-in context stores provide both modes. Some stores may only provide asynchronous access and will throw an error if they are accessed synchronously.
To get a value from context:
var myCount = flow.get("count");
To set a value:
flow.set("count", 123);
The following example maintains a count of how many times the function has been run:
// 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;
Get/Set multiple values
It is also possible to get or set multiple values in one go:
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
flow.set(["count", "colour", "temperature"], [123, "red", "12.5"]);
In this case, any missing values are set to null
.
Asynchronous context access
If the context store requires asynchronous access, the get
and set
functions require an extra callback parameter.
// 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) { ... })
The first argument passed to the callback, err
, is only set if an error occurred when accessing context.
The asynchronous version of the count example becomes:
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); } }); } });
Multiple context stores
It is possible to configure multiple context stores. For example, both a memory
and file
based store could be used.
The get
/set
context functions accept an optional parameter to identify the store to use.
// 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) { ... })
Adding Status
The function node can also provide its own status decoration. To set the status, call the node.status
function. For example
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
Any status updates can then also be caught by the Status node.
Loading additional modules
Additional node modules cannot be loaded directly within a Function node. Refer, What external libraries and modules are allowed in Experience Designer? for supported modules and libraries.
API Reference
The following objects are available within the Function node.
node
node.id
: the id of the Function nodenode.name
: the name of the Function nodenode.log(..)
: log a messagenode.warn(..)
: log a warning messagenode.error(..)
: log an error messagenode.debug(..)
: log a debug messagenode.trace(..)
: log a trace messagenode.on(..)
: register an event handlernode.status(..)
: update the node statusnode.send(..)
: send a messagenode.done(..)
: finish with a message
context
context.get(..)
: get a node-scoped context propertycontext.set(..)
: set a node-scoped context propertycontext.keys(..)
: return a list of all node-scoped context property keyscontext.flow
: same asflow
context.global
: same asglobal
flow
flow.get(..)
: get a flow-scoped context propertyflow.set(..)
: set a flow-scoped context propertyflow.keys(..)
: return a list of all flow-scoped context property keys
global
global.get(..)
: get a global-scoped context propertyglobal.set(..)
: set a global-scoped context propertyglobal.keys(..)
: return a list of all global-scoped context property keys
env
env.get(..)
: get an environment variable
Other modules and functions
The Function node also makes the following modules and functions available:
Buffer
- the Node.jsBuffer
moduleconsole
- the Node.jsconsole
module (node.log
is the preferred method of logging)util
- the Node.jsutil
modulesetTimeout/clearTimeout
- the javascript timeout functions.setInterval/clearInterval
- the javascript interval functions.
The function node automatically clears any outstanding timeouts or interval timers whenever it is stopped or re-deployed.