async await with setInterval
Asked Answered
B

11

60
function first(){
  console.log('first')
}
function second(){
  console.log('second')
}
let interval = async ()=>{
  await setInterval(first,2000)
  await setInterval(second,2000)
}
interval();

Imagine that I have this code above.

When I run it, first() and second() will be called at the same time; how do I call second() after first)() returns some data, for example, if first() is done, only then call second()?

Because first() in my code will be working with a big amount of data and if this 2 functions will be calling at the same time, it will be hard for the server.

How do I call second() each time when first() will return some data?

Borszcz answered 5/9, 2018 at 11:40 Comment(3)
setInterval never resolves since it's a continous repeater. Not sure if you've thought this through. What exactly do you want to achieve? If you want to run calls every 2 seconds and reacting to it, you might be better off writing a solution with RxJs and using Observables.Torry
Can you explicate your use case?Branchia
When server started , I want to call some functions with some interval - for example each 15 minutes - but this functions works with big data , I can run 2 functions in the same time , but it will be hard for the server to work with I think , I want to make the same run functions with some interval but , with waiting on when first fucntion done ... for example I have first function after 15 min this function executed , it is executes and second function wait , only when first function done , second function start , and so on each time . Hope it is clear .Borszcz
A
78

As mentioned above setInterval does not play well with promises if you do not stop it. In case you clear the interval you can use it like:

async function waitUntil(condition) {
  return await new Promise(resolve => {
    const interval = setInterval(() => {
      if (condition) {
        resolve('foo');
        clearInterval(interval);
      };
    }, 1000);
  });
}

Later you can use it like

const bar = await waitUntil(someConditionHere)
Armilla answered 5/6, 2019 at 23:15 Comment(7)
Though I think the question asked isn't clearly stated, this answer points out the fallacy stated by several people that setInterval doesn't play well with promises; it can play very well if the correct logic is supplied (just as any code has its own requirements to run correctly). I fixed some syntax errors but I think the gist of this answer provides better information than the others. (I don't think it really answers the original question but I'm not sure I know exactly what that question is asking myself.)Amadeo
Then condition should be a callback function.Captivate
Really appreciate this! Thank youVillainy
Here, bar will be instantly assigned a pending Promise, and not wait for it to resolve.Poundage
@Poundage do you mean the last line should be: const bar = async waitUntil(someConditionHere)?Gwendagwendolen
@icc97, I mean that the async before waitUntil declaration and the await inside are totaly useless, cause it wont make waitUntil to "wait". const bar = await waitUntil(someConditionHere) will do. (and again, no need for any async nor await inside declaration)Poundage
@Poundage Ah yeah, sorry, I meant await too :)Gwendagwendolen
M
47

You have a few problems:

  1. Promises may only ever resolve once, setInterval() is meant to call the callback multiple times, Promises do not support this case well.
  2. Neither setInterval(), nor the more appropriate setTimeout() return Promises, therefore, awaiting on them is pointless in this context.

You're looking for a function that returns a Promise which resolves after some times (using setTimeout(), probably, not setInterval()).

Luckily, creating such a function is rather trivial:

async function delay(ms) {
  // return await for better async stack trace support in case of errors.
  return await new Promise(resolve => setTimeout(resolve, ms));
}

With this new delay function, you can implement your desired flow:

function first(){
  console.log('first')
}
function second(){
  console.log('second')
}
let run = async ()=>{
  await delay(2000);
  first();
  await delay(2000)
  second();
}
run();
Moradabad answered 5/9, 2018 at 11:54 Comment(2)
delay should be just util.promisify(setTimeout).Hynes
This is technically not the same, because time to run might vary, leading to inconsistent intervals.Mortar
A
8

setInterval doesn't play well with promises because it triggers a callback multiple times, while promise resolves once.

It seems that it's setTimeout that fits the case. It should be promisified in order to be used with async..await:

async () => {
  await new Promise(resolve => setTimeout(() => resolve(first()), 2000));
  await new Promise(resolve => setTimeout(() => resolve(second()), 2000));
}
Alleyn answered 5/9, 2018 at 11:55 Comment(0)
E
4

await expression causes async to pause until a Promise is settled

so you can directly get the promise's result without await

for me, I want to initiate Http request every 1s

let intervalid 
async function testFunction() {
    intervalid = setInterval(() => {
        // I use axios like: axios.get('/user?ID=12345').then
        new Promise(function(resolve, reject){
            resolve('something')
        }).then(res => {
            if (condition) {
               // do something 
            } else {
               clearInterval(intervalid)
            }    
        })  
    }, 1000)  
}
// you can use this function like
testFunction()
// or stop the setInterval in any place by 
clearInterval(intervalid)
Ethology answered 10/12, 2019 at 5:1 Comment(1)
I don't see why there is an async keyword here. Also, if your promise takes more than one second to resolve, a new one will be created before it can clear the interval if needed.Poundage
I
4

You could use an IFFE. This way you could escape the issue of myInterval not accepting Promise as a return type.

There are cases where you need setInterval, because you want to call some function unknown amount of times with some interval in between. When I faced this problem this turned out to be the most straight-forward solution for me. I hope it help someone :)

For me the use case was that I wanted to send logs to CloudWatch but try not to face the Throttle exception for sending more than 5 logs per second. So I needed to keep my logs and send them as a batch in an interval of 1 second. The solution I'm posting here is what I ended up using.

  async function myAsyncFunc(): Promise<string> {
    return new Promise<string>((resolve) => {
      resolve("hello world");
    });
  }

  function myInterval(): void {
    setInterval(() => {
      void (async () => {
        await myAsyncFunc();
      })();
    }, 5_000);
  }

  // then call like so
  myInterval();
Illicit answered 4/3, 2022 at 10:24 Comment(0)
D
4

Looked through all the answers but still didn't find the correct one that would work exactly how the OP is asked. This is what I used for the same purpose:

async function waitInterval(callback, ms) {
    return new Promise(resolve => {
        let iteration = 0;
        const interval = setInterval(async () => {
            if (await callback(iteration, interval)) {
                resolve();
                clearInterval(interval);
            }
            iteration++;
        }, ms);
    });
}

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

(async () => {
    console.log('start');
    await waitInterval(first, 1000);
    await waitInterval(second, 1000);
    console.log('finish');
})()

In my example, I also put interval iteration count and the timer itself, just in case the caller would need to do something with it. However, it's not necessary

Ducktail answered 18/8, 2022 at 13:57 Comment(0)
D
1

setInterval schedules ticks but an async call might take longer or eventually get out of sync with the desired interval.

The simplest solution is to call setTimeoutafter the awaits complete:

const first = async () => console.log('first');
const second  = async () => console.log('second');

const interval = async () => {
  await first();
  await second();
  // All done, schedule a new tick
  setTimeout(interval, 2000);
}

interval();

PS:
With the above, the wait time might be greater than 2000ms:

...? ms time to complete fn1
...? ms time to complete fn2
...2000 ms timeout

To instead call your functions timely every 2sec, subtract from the 2000ms the time it took to complete the async functions:

const first = async() => console.log('first');
const second = async() => console.log('second');

const interval = async() => {
  const timeStart = Date.now();

  await first();
  await second();

  // All done, schedule a new one
  const timeDiff = Date.now() - timeStart;
  const delay = Math.max(0, 2000 - timeDiff);
  setTimeout(interval, delay);
}

interval();
Dhole answered 20/11, 2023 at 11:26 Comment(0)
L
0

In my case, I needed to iterate through a list of images, pausing in between each, and then a longer pause at the end before re-looping through. I accomplished this by combining several techniques from above, calling my function recursively and awaiting a timeout. If at any point another trigger changes my animationPaused:boolean, my recursive function will exit.

    const loopThroughImages = async() => {
      for (let i=0; i<numberOfImages; i++){
        if (animationPaused) {
          return;
        }
        this.updateImage(i);
        await timeout(700);
      }
      await timeout(1000);
      loopThroughImages();
    }

    loopThroughImages();
Love answered 14/4, 2022 at 21:52 Comment(2)
Combining recursion with loops like this is weird though. Why don't you simply nest two loops?Chicanery
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewSilvers
P
0

Async/await do not make the promises synchronous. To my knowledge, it's just a different syntax for return Promise and .then(). Here i rewrote the async function and left both versions, so you can see what it really does and compare. It's in fact a cascade of Promises.

// by the way no need for async there. the callback does not return a promise, so no need for await.
function waitInterval(callback, ms) {
    return new Promise(resolve => {
        let iteration = 0;
        const interval = setInterval(async () => {
            if (callback(iteration, interval)) {
                resolve();
                clearInterval(interval);
            }
            iteration++;
        }, ms);
    });
}

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

// async function with async/await, this code ...
(async () => {
    console.log('start');
    await waitInterval(first, 1000);
    await waitInterval(second, 1000);
    console.log('finish');
})() //... returns a pending Promise and ...
console.log('i do not wait');

// ... is kinda identical to this code.
// still asynchronous but return Promise statements with then cascade.
(() => {
    console.log('start again');
    return waitInterval(first, 1000).then(() => {
        return waitInterval(second, 1000).then(() => {
                console.log('finish again');
        });
    });
})(); // returns a pending Promise...
console.log('i do not wait either');

You can see the two async functions both execute at the same time. So using promises around intervals here is not very useful, as it's still just intervals, and promises changes nothing, and make things confusing...

As the code is calling callbacks repeatedly into an interval, this is, i think, a cleaner way:

function first(i) {
    console.log(`first: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function second(i) {
    console.log(`second: ${i}`);
    // If the condition below is true the timer finishes
    return i === 5;
}

function executeThroughTime(...callbacks){
    console.log('start');
    let callbackIndex = 0; // to track current callback.
    let timerIndex = 0; // index given to callbacks
    let interval = setInterval(() =>{
        if (callbacks[callbackIndex](timerIndex++)){ // callback return true when it finishes.
            timerIndex = 0; // resets for next callback
            if (++callbackIndex>=callbacks.length){ // if no next callback finish.
                clearInterval(interval);
                console.log('finish');
            }
        }
    },1000)
}

executeThroughTime(first,second);
console.log('and i still do not wait ;)');

Also, this solution execute a callback every secondes. if the callbacks are async requests that takes more than one sec to resolve, and i can't afford for them to overlap, then, instead of doing iterative call with repetitive interval, i would get the request resolution to call the next request (through a timer if i don't want to harass the server).

Here the "recursive" task is called lTask, does pretty much the same as before, except that, as i do not have an interval anymore, i need a new timer each iteration.

// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync1(i) {
    console.log(`first pending: ${i}`);
    return new Promise((resolve) =>{
        setTimeout(() => resolve('got that first big data'), Math.floor(Math.random()*1000)+ 1000);//simulate request that last between 1 and 2 sec.
    }).then((result) =>{
        console.log(`first solved: ${i} ->`, result);
        return i==2;
    });
}
// slow internet request simulation. with a Promise, could be a callback.
function simulateAsync2(i) {
    console.log(`second pending: ${i}`);
    return new Promise((resolve) =>{
        setTimeout(() => resolve('got that second big data'), Math.floor(Math.random()*1000) + 1000);//simulate request that last between 1 and 2 sec.
    }).then((result) =>{ // promise is resolved
        console.log(`second solved: ${i} ->`,result);
        return i==4; // return a promise
    });
}

function executeThroughTime(...asyncCallbacks){
    console.log('start');
    let callbackIndex = 0;
    let timerIndex = 0;
    let lPreviousTime = Date.now();
    let lTask = () => { // timeout callback.
        asyncCallbacks[callbackIndex](timerIndex++).then((result) => { // the setTimeout for the next task is set when the promise is solved.
            console.log('result',result)
            if (result) { // current callback is done.
                timerIndex = 0;
                if (++callbackIndex>=asyncCallbacks.length){//are all callbacks done ?
                    console.log('finish');
                    return;// its over
                }
            }
            console.log('time elapsed since previous call',Date.now() - lPreviousTime);
            lPreviousTime = Date.now();
            //console.log('"wait" 1 sec (but not realy)');
            setTimeout(lTask,1000);//redo task after 1 sec.
            //console.log('i do not wait');
        });
    }
    lTask();// no need to set a timer for first call.
}

executeThroughTime(simulateAsync1,simulateAsync2);
console.log('i do not wait');

Next step would be to empty a fifo with the interval, and fill it with web request promises...

Poundage answered 2/2, 2023 at 2:56 Comment(0)
F
0
import {
  setInterval,
} from 'timers/promises';

const interval = 100;
for await (const startTime of setInterval(interval, Date.now())) {
  const now = Date.now();
  console.log(now);
  if ((now - startTime) > 1000)
    break;
}
console.log(Date.now());

Solution for nodejs.

https://nodejs.org/api/timers.html#timerspromisessetintervaldelay-value-options

Felony answered 4/7, 2023 at 18:29 Comment(0)
T
0

I needed a function to check for something on an interval over a specified amount of iterations, and then optionally fail with an error. I initially wrote this for Nodejs, but you can easily port this to React if you need to.

My personal use case was: imagine you're using an online dating app and two users simultaneously swipe right (yes)? There's a high chance that the end point that handles the logic of the "like" and the subsequent check for an existing "mutual like" will not be able to pick up both likes if they occur in parallel. You could use this function to query your db or Redis over a timeout and n amount of iterations.

useAsyncIntervalCheck

const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));

const useAsyncIntervalCheck = async ({
    // `handler` should return either `true` or `false`
    handler,
    maxIntervalCount = 3,
    errorMessageOnFailedCheck,
    timeout = 250,
}: {
    handler: (currentCount: number) => Promise<boolean>;
    maxIntervalCount?: number;
    errorMessageOnFailedCheck?: string;
    timeout?: number;
}) => {
    // locally track the count of iterations
    let count = 0;
    // initially determine whether or not to enter while loop
    let result = await handler(count);
    while (!result && count + 1 < maxIntervalCount) {
        // keep updating `result` with the return value of `handler`
        result = await handler(count);
        // update current iteration
        count++;

        // you can also optionally specify a timeout between iterations
        if (typeof timeout !== 'undefined') {
            await delay(timeout);
        }
    }

    // you could also optionally throw an error message on failed condition check
    if (!result && errorMessageOnFailedCheck) {
        throw new Error(errorMessageOnFailedCheck);
    }

    // otherwise just return the result
    return !!result;
};

I was initially trying to figure out a solution with setInterval, but quickly realized that this analog is a better approach.

Theolatheologian answered 6/2 at 6:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.