Skip to content

Instantly share code, notes, and snippets.

@Atinux
Last active October 10, 2023 03:04
Show Gist options
  • Save Atinux/fd2bcce63e44a7d3addddc166ce93fb2 to your computer and use it in GitHub Desktop.
Save Atinux/fd2bcce63e44a7d3addddc166ce93fb2 to your computer and use it in GitHub Desktop.
JavaScript: async/await with forEach()
const waitFor = (ms) => new Promise(r => setTimeout(r, ms))
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(50)
console.log(num)
})
console.log('Done')
}
start()
@mesqueeb
Copy link

@Atinux Any chance you can turn this into an npm module?
I've been using this throughout a lot of projects and always add it as a manual helper file. I think that means it's a great candidate for an npm module!

@mattglover11
Copy link

mattglover11 commented Oct 11, 2018

I don't get how you can have an await in the first function?

await callback(array[index], index, array)
^^^^^

SyntaxError: await is only valid in async function
at new Script (vm.js:74:7)
at createScript (vm.js:246:10)
at Object.runInThisContext (vm.js:298:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)

The issue with prefixing the callback with async to enable that await to work means that the net result does not accomplish a serial execution as planned.

@gwynnebaer
Copy link

@mattyglover - the correct version should be:


async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

const start = async () => {
  await asyncForEach([1, 2, 3], async (num) => {
    await waitFor(50)
    console.log(num)
  })
  console.log('Done')
}
start()

@SHEHANhasintha
Copy link

await callback(array[index], index, array)
^^^^^


SyntaxError: await is only valid in async function
at new Script (vm.js:74:7)
at createScript (vm.js:246:10)
at Object.runInThisContext (vm.js:298:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:266:19)

The issue with prefixing the callback with async to enable that await to work means that the net result does not accomplish a serial execution as planned.

This document has an error. you can't have a function without async while await in them.
to fix this, you can do, this. add async infront of the missing function.

const waitFor = (ms) => new Promise(r => setTimeout(r, ms))
const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}

const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(5000)
console.log(num)
})
console.log('Done')
}

start()

@radiumrasheed
Copy link

how can one get the index from this method

@JonnWalker10
Copy link

@radiumrasheed

how can one get the index from this method

The function will return it in the other params, which in SHEHAN's example where omitted:
if you add the ind param to the function it can be used!

const start = async () => {
await asyncForEach([1, 2, 3], async (num,ind) => {
await waitFor(5000)
console.log(num)
console.log(ind)
})
console.log('Done')
}

@Mifrill
Copy link

Mifrill commented Jun 12, 2019

Thank you, man, great implementation!

@Mifrill
Copy link

Mifrill commented Jun 16, 2019

use for (let element of elements), it's simpler, shorter:

const start = async () => {
  for (let num of [1, 2, 3]) {
    await waitFor(50);
    console.log(num);
  }
  console.log('Done');
}
start();

@wiill
Copy link

wiill commented Jun 16, 2019

Thanks for this piece of code, works like a charm...

would it be bad design / coding do define a prototype function for array like this:
Array.prototype.forEachAsync = async function(callback){ // this represents our array for (let index = 0; index < this.length; index++) { // We call AND AWAIT the callback for each entry await callback(this[index], index, this); } };

to later have calls like this:
await allFiles.forEachAsync(async (aFile) => { //DO AND WAIT FOR ASYNC STUFF BEFORE CARRYING ON TO NEXT aFile }

Copy link

ghost commented Jul 19, 2019

I do believe the original forEach from ES6 should understand the async/await call and wait for the inner called code to resolve, and then iterate with a new value.

@pfeilbr
Copy link

pfeilbr commented Nov 7, 2019

Hi, what's 'r' ?

r => setTimeout(r, ms)

r is the resolve parameter (1st param) of the Promise constructor callback function.

@axle21
Copy link

axle21 commented Nov 12, 2019

Hello is there a way that it can be apply using an axios? cos base on what i understand the waitFor is set statically, so how will it wait for a axios response

@wodCZ
Copy link

wodCZ commented Nov 21, 2019

Thanks for the snippet and article!

TypeScript version for anyone interested:

export async function asyncForEach<T>(array: T[], callback: (item: T, index: number, allItems: T[]) => void) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

@dblodorn
Copy link

dblodorn commented Dec 5, 2019

Thanks so much for this snippet and explanation! Implemented in this webpack plugin I wrote that creates a json file from a data endpoint. I wanted to use an array and this was perfect!

https://github.com/dblodorn/fetch-json-webpack-plugin

@CptSpaceToaster
Copy link

CptSpaceToaster commented Mar 1, 2020

The given sample waits for each await to finish before processing the next item in the array. I actually wanted to process my items in parallel but wait for all of them to be done. I wanted to bake 6 cakes, but if each cake takes 10 minutes, I wanted to bake 6 cakes at once and be done in ~10 minutes, instead of 60.

I ended up doing something like this:

export async function asyncForEach<T>(array: T[], callback: (item: T, index: number, allItems: T[]) => void) {
  await Promise.all(array.map(callback));
}

This stack overflow post was helpful in understanding what I wanted: https://stackoverflow.com/a/37576787

@clement-is
Copy link

Thank you very much for this snippet, I passed in parameter of your function asyncForEach, the Object.keys() param, it works wonderfully !

const request = async () => {
            await asyncForEach(Object.keys(category), async (item) => {
                await waitFor(50)
                console.log(item)
                console.log(category[item])
            })
            console.log('Done')
        };
 const { data } = await request();

@WardoPo
Copy link

WardoPo commented Jan 23, 2021

This is so simple yet so brilliant. Kudos

@German-Stepanov
Copy link

Object.defineProperty(Object.prototype, 'eachObjectKey', {writable: true, value:
	async function (p, f, next) {
		var promises = [];
		for (var key in this) {
			if (p=='await') {
				//(in order)
				promises.push(await new Promise( (resolve, reject) => f(key, this[key], resolve)));
			} else {
				//(in parallel)
				promises.push(new Promise( (resolve, reject) => f(key, this[key], resolve)));
			}
		}
		await Promise.all(promises).then(next);
	}
});

var obj = [1, 2, 3, 4];
var f = function(key, value, next) {
	setTimeout(function() {
		console.log(key, value);
		if (value==2) return next('error');
		next('OK');
	}, value*100);
}

console.time('app');
console.log('\nstart in order');
obj.eachObjectKey('await', f, function(results) {
	console.log('Done!');
	console.log(results);
	console.timeEnd('app');

	console.time('app');
	console.log('\nstart in parallel');
	obj.eachObjectKey('not await', f, function(results) {
		console.log('Done!');
		console.log(results);
		console.timeEnd('app')
	});
});

@Razzendah
Copy link

I don´t get why callback reicive 3 arguments, in the start function the call of asyncForEach method the second parameter is callback and is just sending one argument ... can some explain step by step?

@flleeppyy
Copy link

flleeppyy commented Jul 10, 2021

Here's my typescript version (Basically @wodCZ's codeblock) with the ability to return data which is readable in the form of an array

async function asyncForEach<T = any>(array: T[], callback: (value: T, index?: number, array?: T[]) => any): Promise<any[]> {
  const data: any[] = [];
  for (let index = 0; index < array.length; index++) {
    await (async () => {
      const item = await callback(array[index], index, array);
      if (item !== undefined) data.push(item);
      return;
    })();
  }
  return Promise.resolve(data);
}

I feel like this is just a glorified promise.all at this point.

@Korayem
Copy link

Korayem commented May 20, 2023

Why not use for await()?

const waitFor = (ms) => new Promise(r => setTimeout(r, ms))
const start = async () => {
  for await (num of [1, 2, 3]) {
    await waitFor(50)
    console.log(num)
  };
  console.log('Done')
}
start()

More info here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment