TAGS: javascript just for fun

setInterval that blocks on `await`

setInterval and promises in javascript

David Walsh asks on twitter

Does anyone have a good example of chaining promises with setInterval so that multiple promises aren’t created concurrently?

Example of problem

setInterval(async () => {
  const result = await someAsyncFunc();
  
  // The problem is that `someAsyncFunc` could hang during one of the loops,
  // which would allow multiple active awaits
}, 2000);

To expound on this issue:

^ Given my interpretation of this issue, let’s continue.

Take 1: hacking it

My solution: take advantage of js’s ability to set props on functions to track “state”

// this func will wait a random period of time from 1 - 10s
const someAsyncFunc = _ => new Promise((resolve, reject) => {
    const t = Math.random()*10000;
    const now = new Date()
    setTimeout(_ => resolve({t, now}), t)
});
k = setInterval(async function onInterval() {
    // let's recall that functions are just objects in JS
    // we can track a property on the func, isRunning, that
    // will tell us if we are waiting on the asyncFunc to resolve
    if (onInterval.isRunning) {
        // if we are still waiting for the previous func's
        // promise to resolve, just exit
        return
    }

    // if we are here, this is our first iteration OR the async func
    // has resolved
    onInterval.isRunning = true
    const output = await someAsyncFunc()
    const now = new Date()
    console.log(`--------
someAsyncFunc Ran For: ${output.t}, 
Time now vs when someAsyncFunc resolved: ${now.getTime() - output.now.getTime()}, 
Time now: ${new Date()}
---------`)
    onInterval.isRunning = false;
}, 1000)

Output:

--------
someAsyncFunc Ran For: 8261.615781686205, 
Time now vs when someAsyncFunc resolved: 8261, 
Time now: Wed Jan 19 2022 18:51:38 GMT-0500 (Eastern Standard Time)
---------
--------
someAsyncFunc Ran For: 7269.033684469877, 
Time now vs when someAsyncFunc resolved: 7272, 
Time now: Wed Jan 19 2022 18:51:46 GMT-0500 (Eastern Standard Time)
---------
--------
someAsyncFunc Ran For: 7857.849152060381, 
Time now vs when someAsyncFunc resolved: 7850, 
Time now: Wed Jan 19 2022 18:51:54 GMT-0500 (Eastern Standard Time)
---------
clearInterval(k)
undefined
--------
someAsyncFunc Ran For: 8935.12200335714, 
Time now vs when someAsyncFunc resolved: 8933, 
Time now: Wed Jan 19 2022 18:52:03 GMT-0500 (Eastern Standard Time)
---------
// proving clearInterval works
new Date()
Wed Jan 19 2022 18:52:18 GMT-0500 (Eastern Standard Time)

This works! Neat.

Note that:

Take 2: wrapping into a function

To make this code more easily reusable, let’s wrap into a function:

// this func will wait a random period of time from 1 - 10s
const someAsyncFunc = _ => new Promise((resolve, reject) => {
    const t = Math.random()*10000;
    const now = new Date()
    setTimeout(_ => resolve({t, now}), t)
});

// this returns a func that tracks the "state" of 
// our onInterval callback
function setIntervalWithPromise(target) {
    return async function(...args) {
        if (target.isRunning) return

        // if we are here, we can invoke our callback!
        target.isRunning = true
        await target(...args)
        target.isRunning = false
    }  
}

// Now, we can run setInterval as per usual but wrapped in 
// `setIntervalWithPromise`
k = setInterval(setIntervalWithPromise(async function() {
    // here, we can do as we please with promises, etc
    // all BAU
    const output = await someAsyncFunc()
    
    // this is just to prove timing works as expected
    const now = new Date()
    console.log(`someAsyncFunc timeout: ${output.t}, Time now: ${new Date()}`)
}), 1000)

The result is the same:

someAsyncFunc timeout: 8475.130126684691, Time now: Thu Jan 20 2022 02:13:47 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 8176.9764095892515, Time now: Thu Jan 20 2022 02:13:56 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 3362.7717183204654, Time now: Thu Jan 20 2022 02:14:00 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 7939.80037795154, Time now: Thu Jan 20 2022 02:14:09 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 7984.381100145164, Time now: Thu Jan 20 2022 02:14:17 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 5770.4932457582945, Time now: Thu Jan 20 2022 02:14:22 GMT-0500 (Eastern Standard Time)
someAsyncFunc timeout: 1932.7454950995527, Time now: Thu Jan 20 2022 02:14:25 GMT-0500 (Eastern Standard Time)
clearInterval(k)
someAsyncFunc timeout: 7951.747669489057, Time now: Thu Jan 20 2022 02:14:33 GMT-0500 (Eastern Standard Time)
// proof that clearInterval works
new Date()
Thu Jan 20 2022 02:22:05 GMT-0500 (Eastern Standard Time)

Ta da!

Share