JavaScript
Promises
Async
Async.each
Programming

JS what's the promises equivalent of async.each?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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

javascript
1// async.each (callback-based, from the async library)
2const async = require('async');
3
4async.each(userIds, (id, callback) => {
5  fetchUser(id, (err, user) => {
6    if (err) return callback(err);
7    processUser(user);
8    callback();
9  });
10}, (err) => {
11  if (err) console.error('Failed:', err);
12  else console.log('All users processed');
13});
14
15// Promise.all equivalent (native)
16await Promise.all(userIds.map(async (id) => {
17  const user = await fetchUser(id);
18  processUser(user);
19}));
20console.log('All users processed');

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)

javascript
1// async.map — callback returns results
2async.map(urls, (url, callback) => {
3  fetch(url, callback);
4}, (err, results) => {
5  console.log(results);
6});
7
8// Promise.all + map — results in the same order as input
9const results = await Promise.all(urls.map(async (url) => {
10  const response = await fetch(url);
11  return response.json();
12}));
13console.log(results);  // Array of parsed responses, same order as urls

Sequential Processing (async.eachSeries equivalent)

javascript
1// async.eachSeries — one at a time
2async.eachSeries(items, (item, callback) => {
3  processItem(item, callback);
4}, done);
5
6// for...of with await — one at a time
7for (const item of items) {
8  await processItem(item);
9}
10
11// Or with reduce for accumulating results
12const results = [];
13for (const item of items) {
14  const result = await processItem(item);
15  results.push(result);
16}

for...of with await processes items one at a time, waiting for each to complete before starting the next.

Limited Concurrency (async.eachLimit equivalent)

javascript
1// async.eachLimit — at most N concurrent operations
2async.eachLimit(urls, 5, (url, callback) => {
3  fetch(url, callback);
4}, done);
5
6// Promise-based concurrency limiter
7async function eachLimit(items, limit, fn) {
8  const executing = new Set();
9
10  for (const item of items) {
11    const promise = fn(item).then(() => executing.delete(promise));
12    executing.add(promise);
13
14    if (executing.size >= limit) {
15      await Promise.race(executing);
16    }
17  }
18
19  await Promise.all(executing);
20}
21
22// Usage
23await eachLimit(urls, 5, async (url) => {
24  const response = await fetch(url);
25  await saveResponse(url, response);
26});

Or use the p-limit library:

javascript
1import pLimit from 'p-limit';
2
3const limit = pLimit(5);  // Max 5 concurrent
4
5const results = await Promise.all(
6  urls.map(url => limit(() => fetch(url).then(r => r.json())))
7);

Error Handling

javascript
1// async.each stops on first error (like Promise.all)
2async.each(items, (item, callback) => {
3  doWork(item, callback);  // callback(err) stops everything
4}, (err) => {
5  if (err) handleError(err);
6});
7
8// Promise.all rejects on first error
9try {
10  await Promise.all(items.map(item => doWork(item)));
11} catch (err) {
12  handleError(err);  // First rejection
13}
14
15// Promise.allSettled — wait for ALL, even failures
16const results = await Promise.allSettled(items.map(item => doWork(item)));
17
18results.forEach((result, i) => {
19  if (result.status === 'fulfilled') {
20    console.log(`Item ${i} succeeded:`, result.value);
21  } else {
22    console.log(`Item ${i} failed:`, result.reason);
23  }
24});

Promise.allSettled is the equivalent of running async.each with error collection instead of early termination.

Complete Comparison Table

async libraryPromise 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 — forEach ignores the returned promises. Use Promise.all(items.map(...)) or a for...of loop instead.
  • Unhandled rejections in Promise.all: If one promise rejects, Promise.all immediately rejects and the other promises continue running in the background. Their results (or errors) are silently discarded. Use Promise.allSettled if you need all results.
  • Accidentally running everything in parallel: Promise.all(items.map(fn)) starts all operations immediately. If items has 10,000 entries and fn makes HTTP requests, you may overwhelm the server. Use a concurrency limiter for large collections.
  • Sequential processing with .map() instead of for...of: await Promise.all(items.map(async (x) => { await fn(x) })) runs in parallel, not sequentially. The await inside the map callback only pauses that single callback, not the map iteration. Use for...of for 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 (with undefined) while the callback runs asynchronously. Always promisify callback-based functions first.

Summary

  • Promise.all(items.map(fn)) is the direct replacement for async.each
  • Use for...of with await for sequential processing (async.eachSeries)
  • Use p-limit or a custom pool for concurrency-limited processing (async.eachLimit)
  • Promise.allSettled collects all results including failures, unlike Promise.all which rejects on the first error
  • Avoid forEach with async callbacks — it does not await the promises
  • For large collections, always limit concurrency to avoid overwhelming external services

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.