Skip to content

Instantly share code, notes, and snippets.

@atinux
Last active August 30, 2024 13:03
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()
@medmin
Copy link

medmin commented Jan 4, 2018

Hi, what's 'r' ?

r => setTimeout(r, ms)

@stevendelro
Copy link

Thanks for your help!

I've used your example for a redux application that I'm working on and I can't get dispatch inside my function in order to send out the computed data.

BTW, I'm totally not expecting a response, but this is my shot in the dark. I would appreciate any pointers if you had any.

export const fetchDetailsByIDs = async listIDs => {
  let fullDetailArray = [];
  let detailsByIds = {};
  await asyncForEach(listIDs, async imdbID => {
    const url = `${ROOT_URL}?i=${imdbID}${API_KEY}`;
    await axios.get(url).then(response => {
      fullDetailArray.push(response.data);
    });
    //Create a state object for detail reducer
    for (var i = 0; i < fullDetailArray.length; i++) {
      detailsByIds[fullDetailArray[i].imdbID] = fullDetailArray[i];
    }
  });
// The logs below show me the correct data, but I can't get dispatch 
// inside this function at all in order to use it. 
  console.log('DETAIL ARRAY', fullDetailArray);
  console.log('DETAIL STATE OBJECT', detailsByIds);
};


export const fetchMainDataSuccess = movieList => {
  type: FETCH_MAIN_DATA_SUCCESS;
  payload: movieList;
};

@stevendelro
Copy link

I figured it out. I just combined the async/await logic into the promise chain of the reducer before it. I couldn't have done it without your asyncForEach insight. Thanks!

If you were curious:

export const fetchData = searchTerm => {
  const url = `${ROOT_URL}?s=${searchTerm}${API_KEY}`;
  return dispatch => {
    dispatch(fetchStarted());
    axios
      .get(url)
      .then(
        response => Object.keys(mapKeys(response.data.Search, 'imdbID')),
        dispatch(fetchMainData())
      )
      .then(async movieIDs => {
        let fullDetailArray = [];
        await asyncForEach(movieIDs, async imdbID => {
          const url = `${ROOT_URL}?i=${imdbID}${API_KEY}`;
          await axios.get(url).then(response => {
            fullDetailArray.push(response.data);
          });
        });
        console.log('DETAIL ARRAY', fullDetailArray);
        dispatch(fetchMainDataSuccess(fullDetailArray));
      });
  };
};

@patrickbense
Copy link

@medmin

const waitFor = (ms) => new Promise(r => setTimeout(r, ms))

r in the above is the resolve method for promises. That is, a Promise would typically look like this new Promise(resolve, reject) { resolve() }. In the case that you are querying about, reject is omitted. This "waitFor" method is a clever way to get JavaScript to pause for whatever amount of milliseconds you want before proceeding to the next line. While using in an asyncForEach like this example, you force the loop to stop executing for x milliseconds before it prints out the next line then re-enters the for loop again for the next value of the array.

Hope that helps.

@borel
Copy link

borel commented Apr 15, 2018

Hi Patrick ,

Is it better with async on line 2 const asyncForEach = async (array, callback) => { ?

PB

@unandreshevia
Copy link

God, Allah, The universe, or whoever you do believe in bless you. A star for your fine work. Solved my problem perfectly with various ForEach that I needed to execute in which each one depended on the result of the previous.

@randheerschouhan
Copy link

this

const asyncForEach = (array, callback) => {
should be this :
const asyncForEach = async (array, callback) => {

@farzadso
Copy link

I read the gist without reading the author's name.

Then I'm like : "This is something Pooya or Sebastian would do"

And to my surprise it was made by you

@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