Mastering Promise Concurrency: Techniques to Control Async Tasks in JavaScript
This article explains how to manage Promise concurrency in JavaScript, covering built‑in methods like Promise.all, allSettled, race, any, custom throttling functions, third‑party libraries, generator‑based solutions, and message‑queue approaches to improve performance and user experience.
Promise has become the standard way to handle asynchronous operations in JavaScript. When many async tasks need to run concurrently, controlling Promise concurrency is crucial for performance and user experience.
Browser concurrency limits (typically 6‑8 per domain) can cause requests to be blocked.
Server overload may lead to slow responses or crashes.
Resource contention (e.g., database connections) can cause deadlocks.
Poor user experience due to long loading times.
Therefore, we need to control Promise concurrency while maintaining task efficiency and avoiding excessive system load.
Promise.all : Parallel execution, unified return
Promise.allaccepts an array of Promises, runs them in parallel, and resolves with an array of results when all Promises are fulfilled.
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // Output: [1, 2, 3]
});Applicable scenario: Multiple async tasks without dependencies can run in parallel.
Note: If any Promise rejects, Promise.all rejects immediately with the first rejection reason.
Promise.allSettled : Parallel execution, returns all statuses
Promise.allSettledbehaves like Promise.all but waits for every Promise to settle (fulfilled or rejected) and returns an array describing each outcome.
const promise1 = Promise.resolve(1);
const promise2 = Promise.reject("Error");
const promise3 = Promise.resolve(3);
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
console.log(results);
/* Output:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'Error' },
{ status: 'fulfilled', value: 3 }
]
*/
});Applicable scenario: Need results of all Promises regardless of success or failure.
Promise.race : Parallel execution, first settled wins
Promise.racetakes an array of Promises and resolves or rejects as soon as the first Promise settles.
Applicable scenario: Retrieve the fastest result, such as implementing a request timeout.
Promise.any (ES2021): Parallel execution, first fulfilled
Promise.anyresolves with the first fulfilled Promise; if all reject, it rejects with an AggregateError.
Applicable scenario: Need the first successful Promise result.
Custom concurrency control function: limit maximum parallelism
Built‑in methods like Promise.all cannot limit the number of concurrent executions. A custom function can enforce a maximum concurrency limit.
Usage example:
Principle: tasks: array of functions returning a Promise. limit: maximum number of concurrent tasks. results: stores all task results. running: holds currently executing Promises. current: index of the next task to start.
Loop while there are pending tasks or running tasks.
If running.length is below limit and tasks remain, start the next task and push its Promise into running.
Each task.then() adds the result to results and removes the Promise from running.
When running.length reaches limit, await Promise.race(running) waits for any task to finish.
Finally, Promise.all(results) resolves when all tasks are done.
Third‑party libraries: p-limit , async-pool
Several mature libraries simplify Promise concurrency control. p-limit: a lightweight concurrency‑control library.
async-pool: supports multiple concurrency strategies.
Using Generator functions and yield
Generator functions can pause and resume execution; combined with yield, they enable fine‑grained concurrency control.
async function* taskGenerator(tasks) {
for (const task of tasks) {
yield task();
}
}
async function runTasks(tasks, limit) {
let pool = [];
let results = [];
for await (let result of taskGenerator(tasks)) {
pool.push(result);
results.push(result);
if (pool.length >= limit) {
await Promise.race(pool);
pool = pool.filter(p => p.status != 'fulfilled' && p.status != 'rejected'); // manual cleanup
}
}
return Promise.all(results);
}Using message queues
For a very large number of async tasks where some delay is acceptable, you can employ message queues (e.g., RabbitMQ, Kafka) to enqueue tasks and let multiple consumers process them in parallel.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaScript
Provides JavaScript enthusiasts with tutorials and experience sharing on web front‑end technologies, including JavaScript, Node.js, Deno, Vue.js, React, Angular, HTML5, CSS3, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
