Linkcall continue flow on timeout

I would like to have the abillity to continue a flow even if there was a timeout. Best option would be to have the linkcall node to have a second output. First one is success and second one is failure (eg: timeout).
That way the flow could continue with other stuff like fallback procedures or cleanups.

This is also useful when using the new "node.linkcall" function call. When inside code I would expect "await node.linkcall('LI test linkcall', msg, {timeout: 1000});" to return something even if there was a timeout.

Welcome to the forum @ZTHawk

You can use a catch node to continue processing if a link call times out.
It has access to the original message properties too:

Your suggestion for inside a function node seems good. :smiley:

Disadvantage of using catch nodes is that I have a global catch per flow. This is a general catch so every (unhandled) error is logged.

Wrong assumption

Using a specific catch will allow to continue the flow but the general error will be called too. Which is wrong in that case.
Changing the global catch so exclude a single case is not possible. It has to be changed to a whitelist catch. So adding new nodes would require to update that global catch over and over again (with every added node).

Edit: there is a "do not handle if handled by other catch" option.

So yes, on node level there is a possiblity. But on code level there is none.

Anyway: The catch will handle all errors. I am only interested in timeouts. Starting to filter timeouts and then rethrow (if that is possible) others seems a bit complicated.

Its pretty simple, put the subroutine (link-in--other-nodes--link-out) into a group and add a catch node that catches "all in group" then pass the message back to the link-out(return) node. After your link call node, use a switch to check for the presence of msg.error & route it accordingly.

Edit: I realise that doesn't handle legitimate timeout (msg not reaching link-out) so you can also group the link call and add a catch all in group again.
I add this because a contrib node (or custom function) might not throw (it may just not return a msg) so a timeout is at the link call is the fail safe.

Not sure if I understood that correctly. Here is what I tried:

[{"id":"acbec43e8e20a59c","type":"inject","z":"4a68d323e0092f46","name":"test wrapper","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":720,"y":250,"wires":[["c385767f68bd1b54"]]},{"id":"c385767f68bd1b54","type":"link call","z":"4a68d323e0092f46","name":"Call wrapper 2s delay","links":["2ea9a7f96384f401"],"linkType":"static","timeout":"2","x":910,"y":250,"wires":[["7a8df5d23fa4d6e8"]]},{"id":"7a8df5d23fa4d6e8","type":"debug","z":"4a68d323e0092f46","name":"debug wrapper","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1110,"y":250,"wires":[]},{"id":"c8238896cba21c6f","type":"inject","z":"4a68d323e0092f46","name":"Test main","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":250,"wires":[["d007adc29f89fb2d"]]},{"id":"d007adc29f89fb2d","type":"link call","z":"4a68d323e0092f46","name":"Call main 2s delay","links":["4ba2090d4005f97f"],"linkType":"static","timeout":"2","x":280,"y":250,"wires":[["66fe37cd7329a12f"]]},{"id":"66fe37cd7329a12f","type":"debug","z":"4a68d323e0092f46","name":"debug main","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":460,"y":250,"wires":[]},{"id":"05f3137e013198b1","type":"group","z":"4a68d323e0092f46","name":"LC main","style":{"label":true},"nodes":["4ba2090d4005f97f","5c309b39e18b0691","f6b0a24f6c0856a9","58bef2c207f43365","ab90f44496eb94a8"],"x":24,"y":29,"w":542,"h":142},{"id":"4ba2090d4005f97f","type":"link in","z":"4a68d323e0092f46","g":"05f3137e013198b1","name":"LI test linkcall","links":[],"x":65,"y":70,"wires":[["58bef2c207f43365","f6b0a24f6c0856a9"]]},{"id":"5c309b39e18b0691","type":"link out","z":"4a68d323e0092f46","g":"05f3137e013198b1","name":"link out 113","mode":"return","links":[],"x":525,"y":70,"wires":[]},{"id":"f6b0a24f6c0856a9","type":"delay","z":"4a68d323e0092f46","g":"05f3137e013198b1","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":190,"y":70,"wires":[["5c309b39e18b0691","ab90f44496eb94a8"]]},{"id":"58bef2c207f43365","type":"debug","z":"4a68d323e0092f46","g":"05f3137e013198b1","name":"main entry","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":190,"y":130,"wires":[]},{"id":"ab90f44496eb94a8","type":"debug","z":"4a68d323e0092f46","g":"05f3137e013198b1","name":"main after delay","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":380,"y":130,"wires":[]},{"id":"dc58f8222a047f5d","type":"group","z":"4a68d323e0092f46","name":"LC wrapper","style":{"label":true},"nodes":["2ea9a7f96384f401","277d5213183e303b","2197ec0f7e42effc","1020f93df07905b7","04fb43cb2393ee54"],"x":614,"y":29,"w":562,"h":122},{"id":"2ea9a7f96384f401","type":"link in","z":"4a68d323e0092f46","g":"dc58f8222a047f5d","name":"LI test linkcall wrapper","links":[],"x":655,"y":70,"wires":[["2197ec0f7e42effc"]]},{"id":"277d5213183e303b","type":"link out","z":"4a68d323e0092f46","g":"dc58f8222a047f5d","name":"link out 114","mode":"return","links":[],"x":985,"y":70,"wires":[]},{"id":"2197ec0f7e42effc","type":"link call","z":"4a68d323e0092f46","g":"dc58f8222a047f5d","name":"Call main 1s delay","links":["4ba2090d4005f97f"],"linkType":"static","timeout":"1","x":820,"y":70,"wires":[["277d5213183e303b"]]},{"id":"1020f93df07905b7","type":"catch","z":"4a68d323e0092f46","g":"dc58f8222a047f5d","name":"","scope":"group","uncaught":false,"x":830,"y":110,"wires":[["04fb43cb2393ee54","277d5213183e303b"]]},{"id":"04fb43cb2393ee54","type":"debug","z":"4a68d323e0092f46","g":"dc58f8222a047f5d","name":"catch inside wrapper","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1030,"y":110,"wires":[]},{"id":"e64260fe0d533965","type":"group","z":"4a68d323e0092f46","name":"Error handling","style":{"label":true,"fill":"#ff0000","fill-opacity":"1"},"nodes":["8979dcb1e4df4953","a3bfdfebb27336a8"],"x":64,"y":339,"w":432,"h":82},{"id":"8979dcb1e4df4953","type":"catch","z":"4a68d323e0092f46","g":"e64260fe0d533965","name":"","scope":null,"uncaught":true,"x":190,"y":380,"wires":[["a3bfdfebb27336a8"]]},{"id":"a3bfdfebb27336a8","type":"debug","z":"4a68d323e0092f46","g":"e64260fe0d533965","name":"catch global","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":380,"y":380,"wires":[]}]
  1. Clicking "Test main" results in "main entry" - "catch global" - "main after delay" - "debug main"
  2. Clicking "Test wrapper" results in "main entry" - "catch inside wrapper" - "debug wrapper" - "main after delay" - "debug wrapper"

Why am I getting in test 1) a return after timeout? This does not make sense.
Why am I getting in test 2) multiple returns? Again, this does not make sense

Adding manual error handling like that can be quite tedious and introduces a lot of boiler plate if you have many link calls.
It would be cleaner if the link call could handle this internally, so one could make proper use of the catch node.
Timeouts are one error, but it is not clean error handling if I want to fail-fast or forward errors from inside the link call flow.

Of course, one would still need to add a catch node for the link call flow nodes and wire it to the link-return, as you described.

That would also be beneficial to your new "link call inside functions" feature. One could use a proper try/catch block inside the function and handle all errors from the link call.

I think this has been discussed either on Github or here, I couldn't find it right away.
There was a discussion about whether the link call should throw an exception that could be handled by the catch node, if there is a msg.error present.
AFAIR it didn't reach a consensus, so it never got implemented.

That is the main reason why I still prefer subflows over link calls, because I can use a clean try/catch approach for error handling.