mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Remove all Promises from Switch node
Promises are expensive and should not be used in the main message handling path. The Switch node used them a lot if the node references context - with a lot of duplicate code to handle async and sync code paths. This change modifies the code to use callbacks throughout that are just as performant in either case.
This commit is contained in:
parent
7f5d47f39d
commit
473a2ae275
@ -91,49 +91,29 @@ module.exports = function(RED) {
|
|||||||
return _maxKeptCount;
|
return _maxKeptCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProperty(node,msg) {
|
function getProperty(node,msg,done) {
|
||||||
if (node.useAsyncRules) {
|
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
if (node.propertyType === 'jsonata') {
|
if (node.propertyType === 'jsonata') {
|
||||||
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
|
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined,value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
|
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(undefined);
|
done(undefined,undefined);
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined,value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (node.propertyType === 'jsonata') {
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateJSONataExpression(node.property,msg);
|
|
||||||
} catch(err) {
|
|
||||||
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
|
|
||||||
} catch(err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getV1(node,msg,rule,hasParts) {
|
function getV1(node,msg,rule,hasParts,done) {
|
||||||
if (node.useAsyncRules) {
|
|
||||||
return new Promise( (resolve,reject) => {
|
|
||||||
if (rule.vt === 'prev') {
|
if (rule.vt === 'prev') {
|
||||||
resolve(node.previousValue);
|
return done(undefined,node.previousValue);
|
||||||
} else if (rule.vt === 'jsonata') {
|
} else if (rule.vt === 'jsonata') {
|
||||||
var exp = rule.v;
|
var exp = rule.v;
|
||||||
if (rule.t === 'jsonata_exp') {
|
if (rule.t === 'jsonata_exp') {
|
||||||
@ -144,115 +124,65 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
|
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (rule.vt === 'json') {
|
} else if (rule.vt === 'json') {
|
||||||
resolve("json"); // TODO: ?! invalid case
|
done(undefined,"json"); // TODO: ?! invalid case
|
||||||
} else if (rule.vt === 'null') {
|
} else if (rule.vt === 'null') {
|
||||||
resolve("null");
|
done(undefined,"null");
|
||||||
} else {
|
} else {
|
||||||
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
|
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(undefined);
|
done(undefined, undefined);
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (rule.vt === 'prev') {
|
|
||||||
return node.previousValue;
|
|
||||||
} else if (rule.vt === 'jsonata') {
|
|
||||||
var exp = rule.v;
|
|
||||||
if (rule.t === 'jsonata_exp') {
|
|
||||||
if (hasParts) {
|
|
||||||
exp.assign("I", msg.parts.index);
|
|
||||||
exp.assign("N", msg.parts.count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateJSONataExpression(exp,msg);
|
|
||||||
} catch(err) {
|
|
||||||
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
|
|
||||||
}
|
|
||||||
} else if (rule.vt === 'json') {
|
|
||||||
return "json"; // TODO: ?! invalid case
|
|
||||||
} else if (rule.vt === 'null') {
|
|
||||||
return "null";
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
|
|
||||||
} catch(err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getV2(node,msg,rule) {
|
function getV2(node,msg,rule,done) {
|
||||||
if (node.useAsyncRules) {
|
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
var v2 = rule.v2;
|
var v2 = rule.v2;
|
||||||
if (rule.v2t === 'prev') {
|
if (rule.v2t === 'prev') {
|
||||||
resolve(node.previousValue);
|
return done(undefined,node.previousValue);
|
||||||
} else if (rule.v2t === 'jsonata') {
|
} else if (rule.v2t === 'jsonata') {
|
||||||
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
|
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
done(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined,value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (typeof v2 !== 'undefined') {
|
} else if (typeof v2 !== 'undefined') {
|
||||||
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
|
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
|
||||||
if (err) {
|
if (err) {
|
||||||
resolve(undefined);
|
done(undefined,undefined);
|
||||||
} else {
|
} else {
|
||||||
resolve(value);
|
done(undefined,value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve(v2);
|
done(undefined,v2);
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var v2 = rule.v2;
|
|
||||||
if (rule.v2t === 'prev') {
|
|
||||||
return node.previousValue;
|
|
||||||
} else if (rule.v2t === 'jsonata') {
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateJSONataExpression(rule.v2,msg);
|
|
||||||
} catch(err) {
|
|
||||||
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
|
|
||||||
}
|
|
||||||
} else if (typeof v2 !== 'undefined') {
|
|
||||||
try {
|
|
||||||
return RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
|
|
||||||
} catch(err) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return v2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRule(node, msg, property, state) {
|
function applyRule(node, msg, property, state, done) {
|
||||||
if (node.useAsyncRules) {
|
|
||||||
return new Promise((resolve,reject) => {
|
|
||||||
|
|
||||||
var rule = node.rules[state.currentRule];
|
var rule = node.rules[state.currentRule];
|
||||||
var v1,v2;
|
var v1,v2;
|
||||||
|
|
||||||
getV1(node,msg,rule,state.hasParts).then(value => {
|
getV1(node,msg,rule,state.hasParts, (err,value) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
v1 = value;
|
v1 = value;
|
||||||
}).then(()=>getV2(node,msg,rule)).then(value => {
|
getV2(node,msg,rule, (err,value) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
v2 = value;
|
v2 = value;
|
||||||
}).then(() => {
|
|
||||||
if (rule.t == "else") {
|
if (rule.t == "else") {
|
||||||
property = state.elseflag;
|
property = state.elseflag;
|
||||||
state.elseflag = true;
|
state.elseflag = true;
|
||||||
@ -261,36 +191,17 @@ module.exports = function(RED) {
|
|||||||
state.onward.push(msg);
|
state.onward.push(msg);
|
||||||
state.elseflag = false;
|
state.elseflag = false;
|
||||||
if (node.checkall == "false") {
|
if (node.checkall == "false") {
|
||||||
return resolve(false);
|
return done(undefined,false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.onward.push(null);
|
state.onward.push(null);
|
||||||
}
|
}
|
||||||
resolve(state.currentRule < node.rules.length - 1);
|
done(undefined, state.currentRule < node.rules.length - 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var rule = node.rules[state.currentRule];
|
|
||||||
var v1 = getV1(node,msg,rule,state.hasParts);
|
|
||||||
var v2 = getV2(node,msg,rule);
|
|
||||||
if (rule.t == "else") {
|
|
||||||
property = state.elseflag;
|
|
||||||
state.elseflag = true;
|
|
||||||
}
|
|
||||||
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
|
|
||||||
state.onward.push(msg);
|
|
||||||
state.elseflag = false;
|
|
||||||
if (node.checkall == "false") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.onward.push(null);
|
|
||||||
}
|
|
||||||
return state.currentRule < node.rules.length - 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRules(node, msg, property,state) {
|
function applyRules(node, msg, property,state,done) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
state = {
|
state = {
|
||||||
currentRule: 0,
|
currentRule: 0,
|
||||||
@ -301,26 +212,18 @@ module.exports = function(RED) {
|
|||||||
msg.parts.hasOwnProperty("index")
|
msg.parts.hasOwnProperty("index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.useAsyncRules) {
|
applyRule(node,msg,property,state,(err,hasMore) => {
|
||||||
return applyRule(node,msg,property,state).then(hasMore => {
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
state.currentRule++;
|
state.currentRule++;
|
||||||
return applyRules(node,msg,property,state);
|
applyRules(node,msg,property,state,done);
|
||||||
} else {
|
} else {
|
||||||
node.previousValue = property;
|
node.previousValue = property;
|
||||||
return state.onward;
|
done(undefined,state.onward);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
var hasMore = applyRule(node,msg,property,state);
|
|
||||||
if (hasMore) {
|
|
||||||
state.currentRule++;
|
|
||||||
return applyRules(node,msg,property,state);
|
|
||||||
} else {
|
|
||||||
node.previousValue = property;
|
|
||||||
return state.onward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -345,13 +248,6 @@ module.exports = function(RED) {
|
|||||||
var valid = true;
|
var valid = true;
|
||||||
var repair = n.repair;
|
var repair = n.repair;
|
||||||
var needsCount = repair;
|
var needsCount = repair;
|
||||||
this.useAsyncRules = (
|
|
||||||
this.propertyType === 'flow' ||
|
|
||||||
this.propertyType === 'global' || (
|
|
||||||
this.propertyType === 'jsonata' &&
|
|
||||||
/\$(flow|global)Context/.test(this.property)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i=0; i<this.rules.length; i+=1) {
|
for (var i=0; i<this.rules.length; i+=1) {
|
||||||
var rule = this.rules[i];
|
var rule = this.rules[i];
|
||||||
@ -363,13 +259,6 @@ module.exports = function(RED) {
|
|||||||
rule.vt = 'str';
|
rule.vt = 'str';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.useAsyncRules = this.useAsyncRules || (
|
|
||||||
rule.vt === 'flow' ||
|
|
||||||
rule.vt === 'global' || (
|
|
||||||
rule.vt === 'jsonata' &&
|
|
||||||
/\$(flow|global)Context/.test(rule.v)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (rule.vt === 'num') {
|
if (rule.vt === 'num') {
|
||||||
if (!isNaN(Number(rule.v))) {
|
if (!isNaN(Number(rule.v))) {
|
||||||
rule.v = Number(rule.v);
|
rule.v = Number(rule.v);
|
||||||
@ -382,9 +271,6 @@ module.exports = function(RED) {
|
|||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rule.vt === 'flow' || rule.vt === 'global' || rule.vt === 'jsonata') {
|
|
||||||
this.useAsyncRules = true;
|
|
||||||
}
|
|
||||||
if (typeof rule.v2 !== 'undefined') {
|
if (typeof rule.v2 !== 'undefined') {
|
||||||
if (!rule.v2t) {
|
if (!rule.v2t) {
|
||||||
if (!isNaN(Number(rule.v2))) {
|
if (!isNaN(Number(rule.v2))) {
|
||||||
@ -393,13 +279,6 @@ module.exports = function(RED) {
|
|||||||
rule.v2t = 'str';
|
rule.v2t = 'str';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.useAsyncRules = this.useAsyncRules || (
|
|
||||||
rule.v2t === 'flow' ||
|
|
||||||
rule.v2t === 'global' || (
|
|
||||||
rule.v2t === 'jsonata' &&
|
|
||||||
/\$(flow|global)Context/.test(rule.v2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (rule.v2t === 'num') {
|
if (rule.v2t === 'num') {
|
||||||
rule.v2 = Number(rule.v2);
|
rule.v2 = Number(rule.v2);
|
||||||
} else if (rule.v2t === 'jsonata') {
|
} else if (rule.v2t === 'jsonata') {
|
||||||
@ -444,26 +323,38 @@ module.exports = function(RED) {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drainMessageGroup(msgs,count,done) {
|
||||||
function addMessageToPending(msg) {
|
var msg = msgs.shift();
|
||||||
|
msg.parts.count = count;
|
||||||
|
processMessage(msg,false, err => {
|
||||||
|
if (err) {
|
||||||
|
done(err);
|
||||||
|
} else {
|
||||||
|
if (msgs.length === 0) {
|
||||||
|
done()
|
||||||
|
} else {
|
||||||
|
drainMessageGroup(msgs,count,done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function addMessageToPending(msg,done) {
|
||||||
var parts = msg.parts;
|
var parts = msg.parts;
|
||||||
// We've already checked the msg.parts has the require bits
|
// We've already checked the msg.parts has the require bits
|
||||||
var group = addMessageToGroup(parts.id, msg, parts);
|
var group = addMessageToGroup(parts.id, msg, parts);
|
||||||
var msgs = group.msgs;
|
var msgs = group.msgs;
|
||||||
var count = group.count;
|
var count = group.count;
|
||||||
if (count === msgs.length) {
|
var msgsCount = msgs.length;
|
||||||
|
if (count === msgsCount) {
|
||||||
// We have a complete group - send the individual parts
|
// We have a complete group - send the individual parts
|
||||||
return msgs.reduce((promise, msg) => {
|
drainMessageGroup(msgs,count,err => {
|
||||||
return promise.then((result) => {
|
pendingCount -= msgsCount;
|
||||||
msg.parts.count = count;
|
|
||||||
return processMessage(msg, false);
|
|
||||||
})
|
|
||||||
}, Promise.resolve()).then( () => {
|
|
||||||
pendingCount -= group.msgs.length;
|
|
||||||
delete pendingIn[parts.id];
|
delete pendingIn[parts.id];
|
||||||
});
|
done();
|
||||||
|
})
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendGroup(onwards, port_count) {
|
function sendGroup(onwards, port_count) {
|
||||||
@ -529,43 +420,33 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processMessage(msg, checkParts, done) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function processMessage(msg, checkParts) {
|
|
||||||
var hasParts = msg.hasOwnProperty("parts") &&
|
var hasParts = msg.hasOwnProperty("parts") &&
|
||||||
msg.parts.hasOwnProperty("id") &&
|
msg.parts.hasOwnProperty("id") &&
|
||||||
msg.parts.hasOwnProperty("index");
|
msg.parts.hasOwnProperty("index");
|
||||||
|
|
||||||
if (needsCount && checkParts && hasParts) {
|
if (needsCount && checkParts && hasParts) {
|
||||||
return addMessageToPending(msg);
|
addMessageToPending(msg,done);
|
||||||
}
|
} else {
|
||||||
if (node.useAsyncRules) {
|
getProperty(node,msg,(err,property) => {
|
||||||
return getProperty(node,msg)
|
if (err) {
|
||||||
.then(property => applyRules(node,msg,property))
|
node.warn(err);
|
||||||
.then(onward => {
|
done();
|
||||||
|
} else {
|
||||||
|
applyRules(node,msg,property,undefined,(err,onward) => {
|
||||||
|
if (err) {
|
||||||
|
node.warn(err);
|
||||||
|
} else {
|
||||||
if (!repair || !hasParts) {
|
if (!repair || !hasParts) {
|
||||||
node.send(onward);
|
node.send(onward);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
sendGroupMessages(onward, msg);
|
sendGroupMessages(onward, msg);
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}
|
||||||
node.warn(err);
|
done();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var property = getProperty(node,msg);
|
|
||||||
var onward = applyRules(node,msg,property);
|
|
||||||
if (!repair || !hasParts) {
|
|
||||||
node.send(onward);
|
|
||||||
} else {
|
|
||||||
sendGroupMessages(onward, msg);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
node.warn(err);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,12 +459,13 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pendingMessages = [];
|
var pendingMessages = [];
|
||||||
var activeMessagePromise = null;
|
var handlingMessage = false;
|
||||||
var processMessageQueue = function(msg) {
|
var processMessageQueue = function(msg) {
|
||||||
if (msg) {
|
if (msg) {
|
||||||
|
|
||||||
// A new message has arrived - add it to the message queue
|
// A new message has arrived - add it to the message queue
|
||||||
pendingMessages.push(msg);
|
pendingMessages.push(msg);
|
||||||
if (activeMessagePromise !== null) {
|
if (handlingMessage) {
|
||||||
// The node is currently processing a message, so do nothing
|
// The node is currently processing a message, so do nothing
|
||||||
// more with this message
|
// more with this message
|
||||||
return;
|
return;
|
||||||
@ -592,27 +474,24 @@ module.exports = function(RED) {
|
|||||||
if (pendingMessages.length === 0) {
|
if (pendingMessages.length === 0) {
|
||||||
// There are no more messages to process, clear the active flag
|
// There are no more messages to process, clear the active flag
|
||||||
// and return
|
// and return
|
||||||
activeMessagePromise = null;
|
handlingMessage = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are more messages to process. Get the next message and
|
// There are more messages to process. Get the next message and
|
||||||
// start processing it. Recurse back in to check for any more
|
// start processing it. Recurse back in to check for any more
|
||||||
var nextMsg = pendingMessages.shift();
|
var nextMsg = pendingMessages.shift();
|
||||||
activeMessagePromise = processMessage(nextMsg,true)
|
handlingMessage = true;
|
||||||
.then(processMessageQueue)
|
processMessage(nextMsg,true,err => {
|
||||||
.catch((err) => {
|
if (err) {
|
||||||
node.error(err,nextMsg);
|
node.error(err,nextMsg);
|
||||||
return processMessageQueue();
|
}
|
||||||
|
processMessageQueue()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on('input', function(msg) {
|
this.on('input', function(msg) {
|
||||||
if (node.useAsyncRules) {
|
|
||||||
processMessageQueue(msg);
|
processMessageQueue(msg);
|
||||||
} else {
|
|
||||||
processMessage(msg,true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('close', function() {
|
this.on('close', function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user