Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active May 25, 2025 02:02
Show Gist options
  • Select an option

  • Save psenger/491c107e4f754b57d729c931bde7493a to your computer and use it in GitHub Desktop.

Select an option

Save psenger/491c107e4f754b57d729c931bde7493a to your computer and use it in GitHub Desktop.
[Design Pattern: Promise All - Deconstructed with Concurrency] #JavaScript #Promise
const assert = require('assert');
/**
* Promise ALL, technique v1 - an approach to understanding Promise.All
* @param promises
* @returns {*}
* @constructor
*/
const PromiseAll = function PromiseAll ( promises = [] ) {
promises = promises === null ? [] : promises;
const results = [];
let completedPromises = 0;
return new Promise( (resolve, reject) => {
promises.forEach( (promise, index) => {
promise.then( value => {
results[index] = value;
completedPromises += 1;
if(completedPromises === promises.length) {
resolve(results);
}
}).catch(function (error) {
reject(error);
});
});
});
};
/**
* Promise ALL, technique v2 - an approach to understanding Promise.All
* @param promises
* @returns {PromiseLike<[]> | Promise<[]>}
* @constructor
*/
const PromiseSeqSettle = function PromiseSeqSettle(promises = []) {
promises = promises === null ? [] : promises;
const results = [];
const reducer = (accum, p) => {
return accum.then(() => p).then(r => results.push(r)).then(() => Promise.resolve());
};
return promises.reduce(reducer, Promise.resolve())
.then(() => results);
};
/**
* Promise ALL, with concurrency control
* @param promises
* @param n - number of concurrent blocks
* @returns {PromiseLike<[]> | Promise<[]>}
* @constructor
*/
const PromiseAllConcurrent = function PromiseAllConcurrent(promises = [], n) {
const chunk = function chunk(array, size) {
if(array.length <= size){
return [array]
}
return [array.slice(0,size), ...chunk(array.slice(size), size)]
};
promises = promises === null ? [] : promises;
const chunked = chunk(promises,n);
const results = [];
const reducer = (accum, p) => {
return accum.then(() => Promise.all(p)).then(r => results.push(...r)).then(() => Promise.resolve());
};
return chunked.map(chunkedMap=>chunkedMap).reduce(reducer, Promise.resolve())
.then(() => results);
};
const later = (delay) => {
return new Promise(function(resolve) {
setTimeout(resolve, delay);
});
};
const getRandomInt = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const ranDm = (index,rnd) => later(rnd).then(() => {
return {index, msg: rnd};
});
const buildTest = (length) => new Array( length ).fill( null ).map((value,index)=>ranDm(index,getRandomInt(500,1000)));
/**
* Both of these techniques, perform well. I would like to see the call stack of a big test for
* a better understanding how they hold up to large amounts of data.
*/
console.time('PromiseAll A');
PromiseAll(buildTest(100)).then((a)=>{
console.timeEnd('PromiseAll A');
}).catch ((error)=>{
console.log(error);
});
console.time('PromiseSeqSettle B');
PromiseSeqSettle(buildTest(100)).then((a)=>{
console.timeEnd('PromiseSeqSettle B');
}).catch ((error)=>{
console.log(error);
});
console.time('PromiseAllConcurrent A');
const testLength = 10000;
PromiseAllConcurrent(buildTest(testLength),20).then((a)=>{
console.timeEnd('PromiseAllConcurrent A');
assert(a.length === testLength )
a.forEach((v,i)=>{
assert(v.index === i )
})
}).catch ((error)=>{
console.log(error);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment