-
-
Save atinux/fd2bcce63e44a7d3addddc166ce93fb2 to your computer and use it in GitHub Desktop.
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() |
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;
};
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));
});
};
};
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.
Hi Patrick ,
Is it better with async on line 2 const asyncForEach = async (array, callback) => {
?
PB
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.
this
const asyncForEach = (array, callback) => {
should be this :
const asyncForEach = async (array, callback) => {
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
@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!
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.
@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()
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()
how can one get the index from this method
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')
}
Thank you, man, great implementation!
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();
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 }
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.
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
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);
}
}
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
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
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();
This is so simple yet so brilliant. Kudos
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')
});
});
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?
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.
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()
Hi, what's 'r' ?
r => setTimeout(r, ms)