"delay" in function

I've seen several requests for a delay feature in the "function" node. The typical answer is that there is no delay method in JavaScript. Although this is true, you can easily build your own like this:

const delay = ms => new Promise(res => setTimeout(res, ms))

for (i = 0; i < 12; i++) {
   node.send({
       payload: `I've waited ${i*75}ms`,
   })
   await delay(75)
}

This enables a more flexible and powerful way of building flows that are dependent on time.

Next example, I turn on 100 spotlights using MQTT, each one being turned on faster than the next, making it feel like it's speeding up.

for (i = 0; i < 100; i++) {
    node.send({
        payload: true,
        topic: `livingroom/spotlights/${i}/set`
    })
    await delay(500 - i * 5)
}
4 Likes

Thanks for this. Moving to FAQ's to make it easier for people to find.

2 Likes

I should also have said that I don't know that using a promise and await is always a good idea here? I can see why you've done it for the specific case you've used. But in general, using await will block the exit of the function node (I think - not tested) which may cause issues in certain cases.

Generally, I use setTimeout directly but write the timeout reference to a context variable so that it can be cancelled if needed.

1 Like

if you do use setTimeout (or setInterval) in a function I think we automatically clear them up on close so unless you need to cancel for other reasons no need to save the handle to it.

Yes we use exactly the same approach. It is very useful to simplify flows for repeated operations that needs a slight delay in each iteration.

I believe it's safe. Timeouts/Intervals should be handled automatically (await/async is just syntactic sugar).

Cancelling execution could be achieved like this (written on phone, untested):

const delay = ms => new Promise(res => context.set("timer", setTimeout(res, ms)))

for (i = 0; i < 12; i++) {
   node.send({
       payload: `I've waited ${i*75}ms`,
   })
   await delay(75)
}
const cancel = () => clearTimeout(context.get("timer"))

if (someCondition) {
    cancel()
}

As mentioned. I'm sure that the code you posted is OK. It is more that I'm not entirely convinced that it would be safe in a general sense since I'm sure that I read that there is something about await being blocking. So if you had a more complex timeout function, the function node wouldn't exit until it completed. If you had a rapid input of msgs to that fn node, that might cause a backlog.

Anyway, not tested but thought it worth mentioning for future reference. The promise and await aren't really necessary.

await does not block the event loop. It is syntactic sugar for Promises that avoids writing code with lots of callbacks/then-blocks.

I don't think I explained it well. I know that it doesn't block the event loop.

I think this explains the possible pitfalls better than I can:
Understanding async-await in JavaScript | by Gokul N K | Better Programming.

Hmm. I went through that article but still struggle to see the potential problem(s).

Nothing is blocking in the sense that the JavaScript engine will stop. I can imagine Node-RED could potentially have an issue though, is that what you meant?

Could you provide an example that demonstrates the potential issue?

If there is a specific problem related to Node-RED, then I would like to file it as a bug. Perhaps I'll even spend some time fixing it.

Yes. You could potentially get a backlog of requests that haven't completed.

As I said, the example given is fine and won't be a problem, this was more about people making assumptions about await as a general solution in all cases. It seems to me that there are edge cases that need care (as with much of JavaScript!)

No "bug", it is a "feature" of how promises with await works. Nothing wrong with node-red.

Alright. Thanks for clearing that up, I understand your point now.

The same danger is true for callbacks that never return or very long timeouts. There has to be something wrong with the code itself. await/async or promises is just a detail.

I don't recommend always using this await-delay approach. I prefer the delay node where appropriate because my flows become more visually understandable. This trick is for the case where more flexibility is required.

2 Likes