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?
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.
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.
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.
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.
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.
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
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);
});
};
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});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With