Skip to content

Instantly share code, notes, and snippets.

@jcouyang
Last active June 2, 2026 16:34
Show Gist options
  • Select an option

  • Save jcouyang/632709f30e12a7879a73e9e132c0d56b to your computer and use it in GitHub Desktop.

Select an option

Save jcouyang/632709f30e12a7879a73e9e132c0d56b to your computer and use it in GitHub Desktop.
Promise All with Limit of Concurrent N

The Promise All Problem

in case of processing a very large array e.g. Promise.all(A_VERY_LARGE_ARRAY_OF_XHR_PROMISE)

which would probably blow you browser memory by trying to send all requests at the same time

solution is limit the concurrent of requests, and wrap promise in thunk

Promise.allConcurrent(2)([()=>fetch('BLAH1'), ()=>fetch('BLAH2'),...()=>fetch('BLAHN')])

if set concurrent to 2, it will send request BLAH1 and BLAH2 at the same time

if BLAH1 return response and resolved, will immediatly send request to BLAH3

in this way promise sending at the same time always keep the limit 2 which we’ve just configed before

describe('promiseAllStepN', function(){
describe('3 tasks, and cucurrent is 2', function(){
let tasks;
beforeEach(function(){
tasks = range(3).map(x=>sinon.stub())
})
describe('1 is finish', function(){
it('will kickoff the third task', function(done){
tasks[0].returns(Promise.resolve(0))
let task2 = tasks[2]
tasks[1].returns(new Promise(resolve=>setTimeout(()=>{
expect(task2.called).to.be.equal(true)
resolve(1)
done()
}, 1000)))
tasks[2].returns(Promise.resolve(2))
return Promise.allConcurrent(2)(tasks).then(x=>console.log(x))
})
})
})
describe('10 tasks, and cucurrent is 3', function(){
let tasks;
beforeEach(function(){
tasks = range(10).map(x=>sinon.stub())
})
describe('1st is finish but 2nd stuck', function(){
it.only('final task will run before 2nd', function(done){
tasks.forEach((task,index) => task.returns(Promise.resolve(index)))
let task10 = tasks[9]
tasks[1].returns(new Promise(resolve=>setTimeout(()=>{
expect(task10.called).to.be.equal(true)
resolve(1)
done()
}, 1000)))
return Promise.allConcurrent(2)(tasks).then(x=>console.log(x))
})
})
})
})
function promiseAllStepN(n, list) {
let tail = list.splice(n)
let head = list
let resolved = []
let processed = 0
return new Promise(resolve=>{
head.forEach(x=>{
let res = x()
resolved.push(res)
res.then(y=>{
runNext()
return y
})
})
function runNext(){
if(processed == tail.length){
resolve(Promise.all(resolved))
}else{
resolved.push(tail[processed]().then(x=>{
runNext()
return x
}))
processed++
}
}
})
}
Promise.allConcurrent = n => list => promiseAllStepN(n, list)
@s100

s100 commented Jun 2, 2026

Copy link
Copy Markdown

A notable drawback with @webshared and @Tartasprint's implementations is that if one of the promises in the queue rejects, the other "threads" continue scheduling all of the remaining work:

import timersPromises from 'node:timers/promises'

const queue = Array(20).fill().map((_, i) => async () => {
  console.log('performing task', i)
  if (i === 7) {
    throw Error('STOP')
  }
  await timersPromises.setTimeout(Math.random() * 1000)
})

try {
  await pAll(queue, 3)
} catch (error) {
  console.error(error)
}
performing task 0
performing task 1
performing task 2
performing task 3
performing task 4
performing task 5
performing task 6
performing task 7
STOP
performing task 8
performing task 9
performing task 10
performing task 11
performing task 12
performing task 13
performing task 14
performing task 15
performing task 16
performing task 17
performing task 18
performing task 19

Note also that the remaining tasks proceed with lower concurrency because one of the "threads" is now dead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment