DEV Community

Jacob McCrumb
Jacob McCrumb

Posted on

18 2

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (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 :)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay