In my React application I have an array of parameters (some IDs, for example), which should be used as a parameters for an ajax call queue. The problem is that array might contain more than 1000 items and if I just recursively make the ajax call using the forEach loop, the browser page eventually stops responding before each of the requests are resolved.
Is there a technique/library, which could allow to send ajax requests, for example, by 5 requests at a time asynchronously, and only when those are finished, proceed the next 5?
Follow up questions:
React - Controlling multiple Ajax Calls
React - Controlling AJAX calls made to the server
if you are not constrained by es version and can use es6 then you should look into async await
async function makeBatchCalls(arrayIds, length) {
let test = arrayIds.reduce(
(rows, key, index) => (index % length == 0
? rows.push([key])
: rows[rows.length - 1].push(key)
) && rows,
[]
);
let Batchresults = [];
//convert them to two dimensionl arrays of given length [[1,2,3,4,5], [6,7,8,9,10]]
for (calls of test) {
Batchresults.push(
await Promise.all(
calls.map((call) => fetch(`https://jsonplaceholder.typicode.com/posts/${call}`))
)
);
}
return Promise.all(Batchresults); //wait for all batch calls to finish
}
makeBatchCalls([1,2,3,4,5,6,7,8,9,10,12,12,13,14,15,16,17,18,19,20],3)
async/await is meant for the exact scenario of awaiting async calls. basically inside the async function until await is resolved the execution is suspended. You would need to understand promises and generators before you start using them.
Ok, let's sort some things out. In JavaScript AJAX requests are asynchronous by nature. You chose to make them synchronous somewhat in you implementation.
What you need to do is have some array of requests, from which you pop results X at a time, wait for them to return, and repeat.
let ids = [a lot of ids here]
while (ids.length > 0) {
let c = ids.splice(0, 5)
let promises = []
for (let i = 0; i < c.length; i++) {
promises.push(fetch("someURL").then(function() {}))
}
Promise.all(promises)
}
Will execute 5 requests simultaneously, wait for them to complete, then fetch next portion of IDs
I had the same problem in a project. What you need is a priority queue in order to control how many request will be performed simultaneously. I'm using this library. Since the p-queue implementation is simple enough to understand and is not that big, I've pasted the code in the snippet below only to show you how it works in the latest lines.
// IMPLEMENTATION ####################
// Port of lower_bound from http://en.cppreference.com/w/cpp/algorithm/lower_bound
// Used to compute insertion index to keep queue sorted after insertion
function lowerBound(array, value, comp) {
let first = 0;
let count = array.length;
while (count > 0) {
const step = (count / 2) | 0;
let it = first + step;
if (comp(array[it], value) <= 0) {
first = ++it;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
class PriorityQueue {
constructor() {
this._queue = [];
}
enqueue(run, opts) {
opts = Object.assign({
priority: 0
}, opts);
const element = {
priority: opts.priority,
run
};
if (this.size && this._queue[this.size - 1].priority >= opts.priority) {
this._queue.push(element);
return;
}
const index = lowerBound(this._queue, element, (a, b) => b.priority - a.priority);
this._queue.splice(index, 0, element);
}
dequeue() {
return this._queue.shift().run;
}
get size() {
return this._queue.length;
}
}
class PQueue {
constructor(opts) {
opts = Object.assign({
concurrency: Infinity,
queueClass: PriorityQueue
}, opts);
if (opts.concurrency < 1) {
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
}
this.queue = new opts.queueClass(); // eslint-disable-line new-cap
this._queueClass = opts.queueClass;
this._pendingCount = 0;
this._concurrency = opts.concurrency;
this._resolveEmpty = () => {};
this._resolveIdle = () => {};
}
_next() {
this._pendingCount--;
if (this.queue.size > 0) {
this.queue.dequeue()();
} else {
this._resolveEmpty();
if (this._pendingCount === 0) {
this._resolveIdle();
}
}
}
add(fn, opts) {
return new Promise((resolve, reject) => {
const run = () => {
this._pendingCount++;
fn().then(
val => {
resolve(val);
this._next();
},
err => {
reject(err);
this._next();
}
);
};
if (this._pendingCount < this._concurrency) {
run();
} else {
this.queue.enqueue(run, opts);
}
});
}
addAll(fns, opts) {
return Promise.all(fns.map(fn => this.add(fn, opts)));
}
clear() {
this.queue = new this._queueClass(); // eslint-disable-line new-cap
}
onEmpty() {
// Instantly resolve if the queue is empty
if (this.queue.size === 0) {
return Promise.resolve();
}
return new Promise(resolve => {
const existingResolve = this._resolveEmpty;
this._resolveEmpty = () => {
existingResolve();
resolve();
};
});
}
onIdle() {
// Instantly resolve if none pending
if (this._pendingCount === 0) {
return Promise.resolve();
}
return new Promise(resolve => {
const existingResolve = this._resolveIdle;
this._resolveIdle = () => {
existingResolve();
resolve();
};
});
}
get size() {
return this.queue.size;
}
get pending() {
return this._pendingCount;
}
}
// TEST ####################
const promises = new PQueue({
concurrency: 4
});
const makePromise = (key, time) => {
let response = null;
return new Promise(resolve => {
setTimeout(() => {
response = `Promise ${key} resolved`;
console.log(response);
resolve(response);
}, time);
});
}
promises.add(() => makePromise('p1', 5000));
promises.add(() => makePromise('p2', 1000));
promises.add(() => makePromise('p3', 3000));
promises.add(() => makePromise('p4', 6000));
promises.add(() => makePromise('p5', 2000));
promises.add(() => makePromise('p6', 1500));
promises.add(() => makePromise('p7', 5500));
promises.add(() => makePromise('p8', 7000));
promises.onIdle().then(() => {
console.log('Promises queue empty.');
});
In these sort of cases, it is best to change in backend where you can process results for thousand inputs send them at one instead of calling from a thousand times. Another way is to use promise I think.
You can also look at this link if it is applicable for you. I think these things answer your question