[Solved] How to use promise.all with a specified number of parallel processing at Node.js


I have a list of promises: [pr1, pr2, pr3, pr4, pr5]

If you have a list of 5 promises you already have 5 things processing in parallel. There is no way to stop it since you have already triggered the processes by creating the promises.

If you want to process only two of them at once you need to NOT CREATE THE PROMISES. Therefore what you need is a list of 5 functions that return promises instead of 5 promises.

What you need is an array of [f1, f2, f3, f4, f5] where f1 will return pr1, f2 will return pr2 etc.

Once you have this all you need to do is to Promise.all() two promises at a time:

const tasks = [f1, f2, f3, f4, f5];

const BATCH_IN_PARALLEL = 2;

async function batchTasks() {
    for (let i=0; i<tasks.length;) {
        let promises = [];

        // create two promises at a time:
        for (let j=0; j<BATCH_IN_PARALLEL && i<tasks.length; i++,j++) {
            let t = tasks[i];
            promises.push(t()); // create the promise here!
        }

        await Promise.all(promises); // wait for the two promises
    }
}

If you need the result of the promises just collect them in an array:

async function batchTasks() {
    let result = [];

    for (let i=0; i<tasks.length;) {
        let promises = [];

        // create two promises at a time:
        for (let j=0; j<BATCH_IN_PARALLEL && i<tasks.length; i++,j++) {
            let t = tasks[i];
            promises.push(t()); // create the promise here!
        }

        result.push(await Promise.all(promises));
    }

    return result;
}

The above is a basic implementation of batching. It only processes two async functions at a time but it waits for both to complete before processing two more. You can get creative and process another function as soon as one is done but the code for that is a bit more involved.

The async-q library has a function that does just this: asyncq.parallelLimit:

const asyncq = require('async-q');

const tasks = [f1, f2, f3, f4, f5];

let result = asyncq.parallelLimit(tasks, 2);

Additional answer

Here is some old code I found in one of my projects that continuously processes two tasks in parallel. It uses a recursive function to process the tasks array until empty. As you can see, the code is a bit more complicated but not too difficult to understand:

function batch (tasks, batch_in_parallel) {
    let len = tasks.length;
    
    return new Promise((resolve, reject)=>{
        let counter = len;

        function looper () {
            if (tasks.length != 0) {
                // remove task from the front of the array:
                // note: alternatively you can use .pop()
                //       to process tasks from the back
                tasks.shift()().then(()=>{
                    counter--;
                    if (counter) { // if we still have tasks
                        looper();  // process another task
                    }
                    else {
                        // if there are no tasks left we are
                        // done so resolve the promise:
                        resolve();
                    }
                });
            }
        }

        // Start parallel tasks:
        for (let i=0; i<batch_in_parallel; i++) {
            looper();
        }
    });
}

// Run two tasks in parallel:
batch([f1, f2, f3, f4, f5], 2).then(console.log('done');

Note that the above function does not return results. You can modify it to collect the result in an array then return the result by passing it to resolve(result) but making sure the result is in the same order as the tasks is not trivial.

Nowdays I’d just use asyncq.parallelLimit() unless I really don’t want to import the entire async-q library or my boss/client does not trust it.

solved How to use promise.all with a specified number of parallel processing at Node.js