I have to run some code after some predefined irregular intervals after users takes some action like a button click.
The intervals could be [1, 1.2, 2.05, 2.8, ...., 10.56]
, [0.2, 1.5, 3.9, 4.67, 6.8, .., 12.0]
or something else. So, the function should be called after 1 second, 1.2 seconds, 2.05 seconds and so on after the initial button click.
How can I do that in JavaScript?
You use setTimeout
inside a function that gets called by that setTimeout
:
// intervals in seconds
const intervals = [1, 1.2, 2.05, 2.8, 10.56];
(function runIrregularly(runThisCode) {
if (intervals.length > 0) {
// get (and remove) the first element from the array:
const timeout = intervals.shift();
// and schedule the next call to run in the future
setTimeout(
() => runIrregularly(runThisCode),
timeout * 1000
);
}
// then run our real function
runThisCode();
})(doSomething);
function doSomething() {
console.log(Date.now());
}
Now, while we're using shift()
to get elements from the start of the array, you should at that point go "hm, so it doesn't even care that it's an array?" and indeed: because this code doesn't iterate over anything, you can even replace that with a generating function that comes up with values as needed:
function getNextInterval() {
// do whatever you like here
return some number;
}
(function runIrregularly(runThisCode) {
if (intervals.length > 0) {
const timeout = getNextInterval();
...
(You could even get fancy by using a generator function if you wanted)
Also, we don't have to call the function "as we declare it" of course, we can also declare and use it separately:
function runIrregularly(runThisCode) {
if (intervals.length > 0) {
...
}
runThisCode();
}
// and then at some later point in the code
runIrregularly(doSomething);
And finally, if you need this to cycle, rather than run once, we can combine shift
with push
, and copy the interval list when we start:
function runIrregularly(runThisCode, intervals) {
// get and remove the first element from the array
const timeout = intervals.shift();
// then push it back on as the last element.
intervals.push(timeout);
setTimeout(
() => runIrregularly(runThisCode, intervals),
timeout * 1000
);
runThisCode();
}
// and then later:
runIrregularly(doSomething, intervals.slice());
With the interval.slice()
being necessary so that we leave the original array pristine (otherwise using for other functions will make those not start at your first interval, but "wherever we are in the cycle" based on however many calls are running).
How to capture and stop the timeouts, I leave as an exercise to the reader (there are plenty of questions/posts about that on the web/SO to find).
You can also achieve this with async/await in combination with setTimeout:
setTimeout(function() {
}, 1000);
Would execute function after 1 second has passed.
You can achieve your goal by nesting setTimeout
calls (see answer by Mike) or with promises (async/await):
function delayExecution(n) {
return new Promise((resolve) => {
setTimeout(resolve, n)
})
}
async function runIrregularly(fn, delays) {
for (const delay of delays) {
// suspend execution by `delay` amounts of seconds
await delayExecution(delay * 1000)
fn()
}
}
runIrregularly(() => {
console.log(Date.now(), "Hello")
}, [1, 2, 3])