JS what's the promises equivalent of async.each?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The Promises equivalent of async.each is Promise.all() combined with Array.map(). Where async.each iterates over a collection and runs an async callback on each item in parallel (calling a final callback when all complete), Promise.all(items.map(fn)) does the same thing with native Promises. For sequential processing (like async.eachSeries), use a for...of loop with await. For limited concurrency (like async.eachLimit), use a concurrency pool pattern or a library like p-limit.
async.each vs Promise.all
Promise.all() starts all promises concurrently and resolves when all complete, or rejects on the first failure — exactly like async.each.
Collecting Results (async.map equivalent)
Sequential Processing (async.eachSeries equivalent)
for...of with await processes items one at a time, waiting for each to complete before starting the next.
Limited Concurrency (async.eachLimit equivalent)
Or use the p-limit library:
Error Handling
Promise.allSettled is the equivalent of running async.each with error collection instead of early termination.
Complete Comparison Table
| async library | Promise equivalent |
async.each(arr, fn, cb) | Promise.all(arr.map(fn)) |
async.eachSeries(arr, fn, cb) | for (const x of arr) await fn(x) |
async.eachLimit(arr, n, fn, cb) | p-limit or custom pool |
async.map(arr, fn, cb) | Promise.all(arr.map(fn)) |
async.filter(arr, fn, cb) | await + manual filter |
async.waterfall([fns], cb) | await fn1(); await fn2(); |
async.parallel([fns], cb) | Promise.all([fn1(), fn2()]) |
async.series([fns], cb) | Sequential await calls |
Common Pitfalls
- Creating promises without awaiting them:
items.forEach(async (item) => { await fn(item) })does not wait for completion —forEachignores the returned promises. UsePromise.all(items.map(...))or afor...ofloop instead. - Unhandled rejections in
Promise.all: If one promise rejects,Promise.allimmediately rejects and the other promises continue running in the background. Their results (or errors) are silently discarded. UsePromise.allSettledif you need all results. - Accidentally running everything in parallel:
Promise.all(items.map(fn))starts all operations immediately. Ifitemshas 10,000 entries andfnmakes HTTP requests, you may overwhelm the server. Use a concurrency limiter for large collections. - Sequential processing with
.map()instead offor...of:await Promise.all(items.map(async (x) => { await fn(x) }))runs in parallel, not sequentially. Theawaitinside the map callback only pauses that single callback, not the map iteration. Usefor...offor sequential execution. - Mixing callbacks and promises: Calling a callback-based function inside a
.map()without wrapping it in a Promise creates a promise that resolves immediately (withundefined) while the callback runs asynchronously. Always promisify callback-based functions first.
Summary
Promise.all(items.map(fn))is the direct replacement forasync.each- Use
for...ofwithawaitfor sequential processing (async.eachSeries) - Use
p-limitor a custom pool for concurrency-limited processing (async.eachLimit) Promise.allSettledcollects all results including failures, unlikePromise.allwhich rejects on the first error- Avoid
forEachwith async callbacks — it does not await the promises - For large collections, always limit concurrency to avoid overwhelming external services

