I created a class to run a certain number of tasks in parallel in the browser. The constructor takes an array of inputs, a function that takes a single input and returns a promise, and a count of the number of tasks to run in parallel. This is for doing some web scraping so I don't want to create a hundred ajax requests and wait for them to all complete with Promise.all
, besides worrying about a timeout or failure I don't want to thrash the connections to a server (jsfiddle):
// run 5 at a time, calling func on each element of delays array
let par = new Parallel(delays, func, 5);
par.run().then(results => { ... });
It runs all the items and resolves if there were no errors, or rejects if there were any errors. The results array contains each result or error from a reject. Is there a better way?
class Parallel {
constructor(inputs, func, count) {
this.inputs = inputs;
this.func = func;
this.count = count;
this.index = 0;
this.active = 0;
this.enabled = true;
this.hadError = false;
}
cancel() {
this.enabled = false;
}
run() {
this.start = performance.now();
return new Promise( (resolve, reject) => {
this.results = [];
let checkDone = () => {
if (this.active === 0 && (this.index >= this.inputs.length || (!this.enabled))) {
this.end = performance.now();
if (this.hadError)
reject(this.results);
else
resolve(this.results);
return true;
}
}
let setAndCheck = (index, result) => {
this.active--;
this.results[index] = result;
queue();
}
let handleOne = (index, input) => {
this.active++;
this.func(input)
.then(result => setAndCheck(index, result))
.catch(err => {
this.hadError = true;
console.log('Error with input:', input);
console.error(err);
setAndCheck(index, err);
});
}
let queue = () => {
if (checkDone()) return;
while (this.enabled && (this.active < this.count) && this.index < this.inputs.length) {
let index = this.index;
let input = this.inputs[index];
this.index = this.index + 1;
handleOne(index, input);
}
}
queue();
});
}
}
let totalDelay = 0;
let func = (delay) => new Promise(resolve => setTimeout(() => resolve(delay), delay));
let delays = [];
for (var i = 0; i < 20; i++) {
let d = Math.random() * 1000 + 500;
delays.push(d);
totalDelay += d;
}
let start = performance.now();
let par = new Parallel(delays, func, 5);
par.run().then(results => {
var total = results.reduce((acc, val) => acc + val, 0);
var actual = performance.now() - start;
document.body.innerHTML = `<ul>
<li>totalDelay: ${totalDelay.toFixed(2)}</li>
<li>actual: ${actual.toFixed(2)}</li>
</ul>`;
});