Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to throttle Promise.all() to 5 promises per second?

I have a couple of items that I need to query a 3rd party API for and said API has a call limit of 5 calls per second. I need to somehow throttle my calls to the API to a maximum of 5 calls per second.

So far I've just used Promise.all() on an array of promises, where each promise sends a request to the API and resolves when the API responds with the HTTP status code 200 and rejects when it responds with some other status code. However, when I have more than 5 items in the array, I risk that the Promise.all() rejects.

How can I limit the Promise.all() call to 5 calls per second?

like image 1000
Hallur A. Avatar asked Dec 27 '18 16:12

Hallur A.


People also ask

How many promises can promise all handle?

all itself as a promise will get resolved once all the ten promises get resolved or any of the ten promises get rejected with an error.

Is there any limit for promise all?

Assuming we have the processing power and that our promises can run in parallel, there is a hard limit of just over 2 million promises.

What does promise all () do?

The Promise. all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises.

Does promise all run promises in parallel?

Promises cannot "be executed". They start their task when they are being created - they represent the results only - and you are executing everything in parallel even before passing them to Promise. all . Promises are executed at the moment of creation.

How do you resolve promises sequentially?

The essence of this function is to use reduce starting with an initial value of Promise. resolve([]) , or a promise containing an empty array. This promise will then be passed into the reduce method as promise . This is the key to chaining each promise together sequentially.


3 Answers

I hope this would help you.

And also to be said this would use Promise.all to resolve all requests and if you have a large list of queries, this would wait for all to resolve and may cause a lot waiting in your code to get all responses. And also if one of request rejects, Promise.all will reject.

I suggest if you don't need all results together it's better to use something else like lodash debounce or throttle or frameworks that handle this.

let items = [
    {name: 'item1'}, 
    {name: 'item2'}, 
    {name: 'item3'}, 
    {name: 'item4'}, 
    {name: 'item5'}, 
    {name: 'item6'}
];

// This is the api request that you send and return a promise
function apiCall(item) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(item.name), 1000);
  })
}

new Promise((resolve) => {
  let results = [];

  function sendReq (itemsList, iterate, apiCall) {
    setTimeout(() => {
      // slice itemsList to send request according to the api limit
      let slicedArray = itemsList.slice(iterate * 5, (iterate * 5 + 5));
      result = slicedArray.map(item => apiCall(item));
      results = [...results, ...result];

      // This will resolve the promise when reaches to the last iteration
      if (iterate === Math.ceil(items.length / 5) - 1) {
          resolve(results);
      }
    }, (1000 * iterate)); // every 1000ms runs (api limit of one second)
  }

  // This will make iteration to split array (requests) to chunks of five items 
  for (i = 0; i < Math.ceil(items.length / 5); i++) {
    sendReq(items, i, apiCall);
  }
}).then(Promise.all.bind(Promise)).then(console.log);
// Use Promise.all to wait for all requests to resolve
// To use it this way binding is required
like image 113
amirhaa Avatar answered Oct 20 '22 04:10

amirhaa


Using ES6 without libraries

export async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}
export function split(arr, n) {
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, n));
  }
  return res;
}
export const delayMS = (t = 200) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(t);
    }, t);
  });
};
export const throttledPromises = (
  asyncFunction,
  items = [],
  batchSize = 1,
  delay = 0
) => {
  return new Promise(async (resolve, reject) => {
    const output = [];
    const batches= split(items, batchSize);
    await asyncForEach(batches, async (batch) => {
      const promises = batch.map(asyncFunction).map(p => p.catch(reject));
      const results = await Promise.all(promises);
      output.push(...results);
      await delayMS(delay);
    });
    resolve(output);
  });
};
like image 10
Adel Avatar answered Oct 20 '22 03:10

Adel


You can use the concurrency option in bluebird if you're not too worried about resolving promises sequentially.

The below would process 5 queries at a time only.

const Promise = require('bluebird');

const buildQueries = (count) => {
  let queries = [];

  for(let i = 0; i < count; i++) {
    queries.push({user: i});
  };

  return queries;
};

const apiCall = (item) => {
  return new Promise(async (resolve, reject) => {
    await Promise.delay(1000);
    resolve(item.user);
  });
};

const queries = buildQueries(20);

Promise.map(queries, async query => {
  console.log( await apiCall(query) );
}, {concurrency: 5});
like image 5
nullspace Avatar answered Oct 20 '22 02:10

nullspace