1

I have an async function that gets called that loops over an array an calls a function for each item.

In this example, the function is hitting an API endpoint and I need to wait for one item to finish before moving onto the next.

However, what currently happens is that each function gets called at roughly the same time, which is causing issues in the api response. So i need to wait 1 second between each request.

This is what I currently have

const delayedLoop = async () => {
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  const myAsyncFunc = async (i) => {
    console.log(`item ${i}`);
    await delay(0);
    return true;
  };

  const arr = ['one', 'two', 'three'];

  const promises = arr.map(
    (_, i) =>
      new Promise((resolve) =>
        setTimeout(async () => {
          await myAsyncFunc(i);
          resolve(true);
        }, 1000),
      ),
  );
  return Promise.all(promises);
}

const myFunc = async () => {
  console.log('START');
  await delayedLoop();
  console.log('FINISH');
}

myFunc();

What happens is;

  • LogsSTART
  • waits 1 second
  • Logs all item ${i} together (without delay in between)
  • Immediately logs FINISH

What I want to happen is

  • LogsSTART
  • waits 1 second
  • Logs item 1
  • waits 1 second
  • Logs item 2
  • waits 1 second
  • Logs item 3
  • Immediately logs FINISH

See JSFiddle to see it in action

mcclosa
  • 943
  • 7
  • 29
  • 59
  • `await delay(0);` seems to be the problem. I think it should be `await delay(1000);` – Emil Karlsson Jul 19 '22 at 12:41
  • 1
    That just extends the time between the last `item ${i}` log and the `FINISH` log, see here https://jsfiddle.net/a4pu6s7q/ – mcclosa Jul 19 '22 at 12:42
  • Oh. Then why does that function exist at all? – Emil Karlsson Jul 19 '22 at 12:43
  • Just to emulate the api call, as it's an async function that performs some logic – mcclosa Jul 19 '22 at 12:44
  • 1
    @EmilKarlsson No, `delay(0)` is not the problem. The problem is `arr.map()` which creates and starts all promises at the same time ... – derpirscher Jul 19 '22 at 12:44
  • Does this answer your question? [How do I serialize a Javascript array of functions returning Promises?](https://stackoverflow.com/questions/50263209/how-do-i-serialize-a-javascript-array-of-functions-returning-promises) – derpirscher Jul 19 '22 at 12:47
  • I see. In that case. you can replace the `1000` with `1000*(i+1)` in the setTimeout within `arr.map`. – Emil Karlsson Jul 19 '22 at 12:48
  • @EmilKarlsson Yes you could. But what if a request takes longer than 1 second? Then there still would be two requests at the same time. Or if most of the requests only take 100ms? Then you will wait 90% of the time for nothing ... There are plenty of sensible ways to serialize an array of promises. Setting a fixed timeout is none of them ... – derpirscher Jul 19 '22 at 12:52
  • @EmilKarlsson No, that's not a good idea. The OP surely wants to keep the promise chain, and that's not going to do that. – Keith Jul 19 '22 at 12:52
  • @derpirscher OP clearly stated what they want to happen. I gave my response based on that. Of course it wouldn't be ideal for optimizing your promises, but that wasn't the question. I don't know why OP wants exactly 1 second between logging each thing, but that's what OP wants, so that's what they get. – Emil Karlsson Jul 19 '22 at 12:56
  • @EmilKarlsson No, that's not what they get. They get a request started every second. If the request itself takes 980ms, the next request will be started in 20ms and not with 1 second in between. If the request takes 3 seconds, there will be parallel requests ... – derpirscher Jul 19 '22 at 12:57

1 Answers1

4

You can do, it like this, using a simple for-loop:

const delayedLoop = async () => {
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  const myAsyncFunc = async (i) => {
    console.log(`item ${i}`);
    return true;
  };

  const arr = ['one', 'two', 'three'];
  for(let i=0; i < arr.length; i++) {
     await myAsyncFunc(i);
     await delay(1000);
  }
}

const myFunc = async () => {
  console.log('START');
  await delayedLoop();
  console.log('FINISH');
}

myFunc();
Charchit Kapoor
  • 8,934
  • 2
  • 8
  • 24
  • 1
    Thanks, this works perfectly! I didn't relalise, as mentioned in another comment, that `arr.map` starts all promises at the same time and using a for loop is the preferred method for this! – mcclosa Jul 19 '22 at 12:47
  • 3
    @mcclosa `arr.map` doesn't start any promises, you do ->`new Promise(`, all map does is map, there is no wait logic built in. There are `map` versions you can use that are promise aware, bluebirds `map` is one, but even better is has a `concurrency` option, and that can come in very handy.. Or even this -> https://www.npmjs.com/package/promise.map – Keith Jul 19 '22 at 12:57