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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

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 :)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more