DEV Community

Jacob McCrumb
Jacob McCrumb

Posted on

Asynchronous setInterval

I recently wanted to kick of a (potentially) long running query against a database, and continue to fire it off 30 seconds after it finished.

Sounds like an easy case of setInterval, but I had my doubts about whether it would work with async (spoiler: it doesn't):

setInterval(async () => {
  console.log('start');
  const promise = new Promise((resolve) => {
    setTimeout(resolve('all done'), 3000);
  });
  await promise;
  console.log('end');
}, 1000);

Not surprising, but disappointingly, it pops out a number of starts before the first end.

And because I might want to do this again one day, decided to write up how I got around it:

const asyncIntervals = [];

const runAsyncInterval = async (cb, interval, intervalIndex) => {
  await cb();
  if (asyncIntervals[intervalIndex]) {
    setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
  }
};

const setAsyncInterval = (cb, interval) => {
  if (cb && typeof cb === "function") {
    const intervalIndex = asyncIntervals.length;
    asyncIntervals.push(true);
    runAsyncInterval(cb, interval, intervalIndex);
    return intervalIndex;
  } else {
    throw new Error('Callback must be a function');
  }
};

const clearAsyncInterval = (intervalIndex) => {
  if (asyncIntervals[intervalIndex]) {
    asyncIntervals[intervalIndex] = false;
  }
};

Then its just a matter of:

setAsyncInterval(async () => {
  console.log('start');
  const promise = new Promise((resolve) => {
    setTimeout(resolve('all done'), 3000);
  });
  await promise;
  console.log('end');
}, 1000);

And if you are tired of it:

clearAsyncInterval(0) // or whatever the return was from setAsyncInterval

Anyways... if you ever find yourself wanting to set an interval that waits for an async function to finish its awaits (as opposed to running as soon as the async function returns its promise), now you know.

Discussion (1)

Collapse
tinnkrit profile image
tinnkrit

Thank you for sharing your technique and it works well. I've improved your code a little bit.

setAsyncInterval

- asyncIntervals.push(true)
+ asyncIntervals.push({run: true, id: 0})

runAsyncInterval

- if (asyncIntervals[intervalIndex]) {
-    setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
+ if (asyncIntervals[intervalIndex].run) {
+    asyncIntervals[intervalIndex].id = setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval)

clearAsyncInterval

- if (asyncIntervals[intervalIndex]) {
-    asyncIntervals[intervalIndex] = false;
+ if (asyncIntervals[intervalIndex].run) {
+     clearTimeout(asyncIntervals[intervalIndex].id)
+     asyncIntervals[intervalIndex].run = false

Hope this help :)