node.js - Control a queue of Promises
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
When Node.js code creates too many promises at once, the event loop stays responsive but the surrounding systems may not. Databases, APIs, and disks can all become overloaded if every task starts immediately. The usual fix is to treat async work as a queue and limit how many promise-producing tasks run at the same time.
Understand the Real Problem
Promise.all does not schedule work gradually. It starts every task as soon as the task function is called. That is fine for a handful of operations, but it becomes a problem when you are sending hundreds of HTTP requests or processing thousands of files.
Bad pattern:
If items is large, every call to processItem starts at once. A queue gives you backpressure by controlling concurrency explicitly.
Sequential Queue
The simplest queue is fully sequential. Only one task runs at a time, which is often enough for rate-limited APIs or order-sensitive work.
This is easy to reason about, but it may be slower than necessary if the downstream system can handle some parallelism.
Concurrency-Limited Queue
Most real systems need something in between sequential execution and unlimited concurrency. A small queue runner can keep, for example, three tasks active at a time.
This approach keeps output ordering stable because results are stored at the original task index, even though execution timing overlaps.
Handle Errors Deliberately
Queue control is not only about concurrency. It is also about deciding what should happen when one task fails. Some queues should stop on the first failure, while others should continue and collect errors for reporting.
This pattern is useful for batch jobs where you want a complete report rather than an immediate stop.
Use Libraries When the Rules Get Richer
If the queue needs retries, priority, pausing, timeouts, or rate limits per second, a library may be more maintainable than custom code. The important point is to understand the underlying model first: the queue should hold task factories, not already-running promises. Once a promise has started, the queue can no longer control it.
Common Pitfalls
- Passing already-created promises into a queue and assuming concurrency will still be limited.
- Using
Promise.allon a large batch and overwhelming the downstream service. - Forgetting to preserve result order when tasks finish at different times.
- Ignoring error strategy and then getting half-finished work with no report.
- Setting the concurrency limit too high and recreating the same overload problem.
Summary
- Queue control in Node.js is really about limiting how many promise-producing tasks start at once.
- Use sequential execution when order matters most and throughput is secondary.
- Use a concurrency-limited worker loop when some parallelism is safe.
- Queue task factories, not active promises.
- Decide early whether failures should stop the queue or be collected for later reporting.
Related reading
- Non-blocking queue of HTTP POST requests with persistence
- Non-recursive depth first search algorithm
- Non-recursive implementation of Flood Fill algorithm?
- Non-Recursive Merge Sort
- NodeJS 7 How to Show the Correct Stack Trace in Async Mongoose Promises
- Node.JS Airtable - Await doesn't wait for promise to be resolved
- Nodejs - Invoke an AWS.Lambda function from within another lambda function
- NodeJS 14.x - Native AWS Lambda Import/Export Support

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.