This is a node-red specific implementation of the command design pattern that helps keeping editor flows transparent.
- each UI input (or any source of input) communicates only with the dispatcher
- each UI input (or any source of input) has a dedicated subflow within which its corresponding logic is implemented
- each dedicated subflow is assigned to a common catch block that handles the unhandled errors in the subflow (and that of the embedded subflows)
- sequential input handling for normal priority messages to make processing synchronous
- high priority message handling to keep asynchronous processing possible as well
- the dispatcher builds a queue of the normal priority incoming inputs (this is the current implementation at least)
- debugging gets easier as all messages pass through the dispatcher (switches can be easily added before the debug nodes to set a filter on the cmdObj properties to narrow down the number of debug messages)
The message sent to the dispatcher must have a cmdObj json object property like the one in the cmdSend node (see example flow below):
msg.cmdObj={
priority: 1,
linkIn: "numeric input",
command: "calculate gauge data",
linkOut: ["gauge", "numeric input"]
}
priority: 1 is normal and 0 is high (actually anything that is not 0 is interpreted as normal). High prio messages get through the dispatcher without being put in the queue.
linkIn: identifies the sender
command: the command identifier that identifies the receiver as well. This is passed to the "link call" node as msg.target which means that it must match the name or id of the receiver link in node.
linkOut: an array in which each element (passed as msg. target to the "link call" node) identifies the link in nodes to which a result shall be sent. Subflows must return. (If a subflow does not reutrn, the dispatcher cannot remove the message being processed from the queue and gets blocked.) The payload returned by a subflow must be an array in which each element is considered as a payload that needs to be distributed to the corresponding linkOut target. The linkOut array elements and the elements of the output payload array of the command subflow are matched by their index. If a certain property needs to be set besides the payload in a returning message, it shall be done in a json object using the 'properties' and 'payload' tags as property names. Example for multiple linkOut targets and returning messages taken from the 'calculate gauge data' subflow:
cmdObj.linkOut array:
linkOut: ["gauge", "numeric input"]
subflow returns:
msg.payload=[msg.payload*2, {properties:{enabled:true},payload:msg.payload}];
Cases when no linkOut is needed can be handled this way:
linkOut: []
subflow returns:
msg.payload=[];
Cases when no message is sent to a certain linkOut target can be handled by sending null:
linkOut: ["target1","target2"]
subflow returns:
msg.payload=["payload2target1",null];
- the message generated by an input on the ui needs to be passed to the dispatcher with the cmdObj property
- the dispatcher either puts it in the queue if there's another message already being processed or releases it immediately
- when a message is released it is sent to the "link call" node which directs it to the corresponding subflow based on its cmdObj.command property (which is copied to msg.target)
- the subflow gets executed
- when the subflow returns its message it is directed to the dispatcher
- the dispatcher identifies the returning message, copies the msg payloads according to the linkOut property to separate messages and sends them to the linkOut targets via the "link call" node
- if there was an error in the subflow and was indicated in the returned message cmdObj.error property, it is copied over to each message cmdObj.error during copying
- the dispatcher deletes the message from the queue, takes the next one and sends it to the "link call" node
- in case of an unhandled exception in any of the subflows, the message queue is reset in order that the dispatcher does not get blocked due to waiting for the returning message. Another implementation could be that a special message (just like the reset) is sent to the dispatcher to remove the last message which could be easily get by the next() method as the js map implementation preserves the order of insertion.
- Either put such a dispatcher logic on each editor tab that corresponds to a ui tab to handle tab specific events or just use it globally.
- On each editor tab use only one ui_control node from which you expect any incoming events and handle that with a command object through the dispatcher.