Delay between promises when using Promise.all
Asked Answered
G

7

23

Is there a way to delay the evaluation of an array of promises using Promise.all()?

Does it make sense to manually add a delay function to the end of each promise before adding them to the array?

Promise.all([p1,p2,p3]).then(res => console.log(res))

I would like to add a delay because my server can't handle too many requests at once.

Gond answered 21/11, 2017 at 18:8 Comment(6)
I don't think this will scale in terms of code, let's say I wanted to call 100 promises. That would result in a very long file. That's why I wanted to try and do it programmatically using Promise.all()Gond
The promise is created when the call is made. Promise.all just notifies you when all calls are done. It doesn't delay them. You need to actually delay the making of the calls, and not the promise.Weightless
That makes a lot of sense, thank you @OriDrori.Gond
Depending on how you're actually creating all those promises, you might be interested in npmjs.com/package/p-limitHigh
Look at async await if you want to serialize calls.Weightless
promise.all isn't why your r equests are happening too quickly. The requests are sent before the promise.all is even called, so there's nothing you can do with promise.all to modify that.Eskridge
A
28

Promise.all is intended to resolve when the promises are fulfilled, but existing promises are evaluated regardless of Promise.all.

In order to do this, promises should be initially created to produce a delay:

const delayIncrement = 500;
let delay = 0;

const p1 = new Promise(resolve => setTimeout(resolve, delay)).then(() => fetch(...));

delay += delayIncrement;

const p2 = new Promise(resolve => setTimeout(resolve, delay)).then(() => fetch(...));

delay += delayIncrement;

...
Promise.all([p1,p2,p3]).then(...);

The same solution can be used for creating request promises in batch inside a loop.

The recipes for delayed promises can be found in this answer.

Admonish answered 21/11, 2017 at 19:7 Comment(1)
Perfect solution!Sergo
C
8

Yes, you can delay promises using Promise.all to create staggered execution and it's quite easy to do:

// Promise.all() with delays for each promise
let tasks = [];
for (let i = 0; i < 10; i++) {
  const delay = 500 * i;
  tasks.push(new Promise(async function(resolve) {
    // the timer/delay
    await new Promise(res => setTimeout(res, delay));

    // the promise you want delayed
    // (for example):
    // let result = await axios.get(...);
    let result = await new Promise(r => {
      console.log("I'm the delayed promise...maybe an API call!");
      r(delay); //result is delay ms for demo purposes
    });

    //resolve outer/original promise with result
    resolve(result);
  }));
}

let results = Promise.all(tasks).then(results => {
  console.log('results: ' + results);
});

You can run it here too.

Rather than a delay between the chain, which can be done with .then() as shown in other answers, this is a delay that differs for each Promise so that when you call Promise.all() they will be staggered. This is useful when, say, you are calling an API with a rate limit that you'd breach by firing all the calls in parallel.

Peace

Container answered 25/6, 2020 at 0:1 Comment(2)
This is the same as @estus-flask's answer but in a loopBlasting
It's a bit different because it's more realistic. You would normally have to loop based on things you don't know ahead of time. Also, it's using await to block versus putting the logic in the setTimeout which doesn't always work, especially if you also have to block there.Container
R
6

I needed to create the calls dynamically, so based on the answer from @estus-flask, managed to come up with:

  let delay = 0; const delayIncrement = 1000;

  const promises = items.map(item => {
    delay += delayIncrement;
    return new Promise(resolve => setTimeout(resolve, delay)).then(() =>
        fetch(...);
  })

  let results = await Promise.all(promises);
Ribbentrop answered 30/9, 2020 at 13:27 Comment(0)
E
3

The simplest solution for me seemed to be to just take the current index of the map function that produces the promises, and use that index to base a delay on:

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

await Promise.all(
  dataPoints.map(async (dataPoint, index) => {
    await sleep(index * 1000)
    ...

This makes each of the operations wait index * 1 second to be fired, effectively placing a 1s delay between each operation.

Enginery answered 23/9, 2021 at 8:40 Comment(2)
This would not workMaximo
@Maximo Because...Enginery
W
0

Is there a way to delay the evaluation of an array of promises using Promise.all()?

No. Promises are not "evaluated", they just resolve. When this happens is determined by their creator and nothing else. When Promise.all is called, the promises p1, p2 and p3 have already been created (and their asynchronous tasks probably already have been started).

Walther answered 21/11, 2017 at 18:35 Comment(0)
T
0

Another way you can do this is by hijacking the way loops are transpiled:

async function doABatchOfAsyncWork(workItems) {
  for (const item of workItems) {
    await workTask(item)
    await delay(1000) // not built-in but easily implemented with setTimeout + promise
  }
}

You can also save the values of course and return them at the end, exactly as you usually can in for-loops. You can't do this with map since the await would have to be in the async context of the map-functor passed in. If you used map it would execute everything at ~the same time, with a delay of 1s at the end.

Tremendous answered 1/4, 2021 at 19:33 Comment(0)
F
-1

This is my solution using async/await.

const requestsWithDelay = async () => {
  let delay = 0;
  const delayIncrement = 1000;
  const items = [...];
  const promises = items.map((n) => {
    delay += delayIncrement;
    return new Promise(async (resolve, reject) => {
      try {
        await setTimeout(async () => {
          await asyncRequest(); // <-- async request
          resolve();
        }, delay);
      } catch (error) {
        reject(error);
      }
    });
  });

  await Promise.all(promises);
};
Fullrigged answered 24/1 at 23:50 Comment(1)
Never pass an async function as the executor to new Promise! Also the await on the setTimeout, and the try/catch around it, are pointless. However, errors in the async callback that is passed to setTimeout are ignored and will break this function.Walther

© 2022 - 2024 — McMap. All rights reserved.