This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 2 years ago.
I'm learning async/await but I really don't know what is wrong here. Why is my myArray always an empty when I use await? How can I solve this?
function getFileAsBinary(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsBinaryString(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
};
const preparePhotos = async (photoFiles, myArray) => {
photoFiles.forEach(async (photoFile) => {
const photoBinary = await getFileAsBinary(photoFile);
myArray.push("something")
});
}
// ----------------------------------------------------
const myArray= [];
const photos = [file1,file2];
await preparePhotos(photos,myArray);
console.log(myArray); // getting [] here, why is it empty?
The callbacks passed to the forEach are not synchronous, it can work, but not as expected. When the iteration is done the async function "preparePhotos" will return immediately.
I think you should use a for loop instead.
PD: not sure if this question is a duplicated of: Using async/await with a forEach loop
When you use await inside the forEach loop, you are telling the js engine to "wait" inside the lambda, not the overall function.
If you want to be able to await the preparePhotos function, you will need to some how get "await" out of the lambda.
Start by defining an array of promises
let promises = [];
Then instead of awaiting the promise returned by getFileAsBinary, append it to the list
photoFiles.forEach((photoFile) => {
promises.push(getFileAsBinary(photoFile));
})
You now have a list of promises, and can use Promise.all to await all promises in the list
let myArray = await Promise.all(promises);
Promise#all returns a list of all values resolved by the promises within the original array, so it will return effectively your original "myArray".
Alternatively, use a for of loop (MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of)
for(let elem of photoFiles) {
let binary = await getFileAsBinary(elem);
myArray.push(binary);
}
Related
I am using Node JS and Express JS, here is my controller code:
const UserComment = require("../model/UserComment");
router.post("/get/comments", async (request, response) =>{
try{
let currentUserID = request.body.userID;
let myUserComment = await UserComment.find({userID: currentUserID});
let friendsCommentsArray = [ ...myUserComment];
let friendsComments = await axios.post(`http://localhost:5000/router/accounts/account/following/list`, {userID: currentUserID})
.then((resp) => {
resp.data.message.map((parentArrayOfArray) =>{
parentArrayOfArray.map((friendID) =>{
let friendsCommentsToLookUp = UserComment.find({userID: friendID})
friendsCommentsToLookUp.then((commentsArray) =>{
commentsArray.map((comment) =>{
if(String(comment.userID) === friendID){
friendsCommentsArray.push(comment);
}else{
console.log("no")
}
})
});
});
});
}).catch((err) =>{
console.log("err: ", err);
throw err;
});
return response.status(200).json({message: friendsPostsArray});
}catch(err){
return response.status(400).json({message: `${err}`});
}
});
The friendsCommentsArray, when I console.log it I can see the data, but when I return it, it’s empty. What is the problem, why is it empty, even though i'm pushing every comment iterated over to the friendsCommentsArray.
However, the returned friendsCommentsArray is empty. how to solve this issue ?
Thanks.
To make await Promise.all() work you need to return the promise
return axios.get(`http://localhost:5000/comments/by/post/${post._id}`)
Generally when you use await, you don't need to use .then(). Your problem is that your inner .map() is using friendsCommentsToLookUp.then(), but nothing is waiting for these promises to resolve before you move on in your code. One might think that you can await the friendsCommentsToLookUp promise, but this won't work, as the calls to the map callback are not awaited.
Removing the .then()'s makes this easier to work with:
const resp = await axios.post(`http://localhost:5000/router/accounts/account/following/list`, {userID: currentUserID});
const message = resp.data.message;
for(const parentArrayOfArray of message) {
for(const friendID of parentArrayOfArray) {
const commentsArray = await UserComment.find({userID: friendID});
for(const comment of commentsArray) {
if(String(comment.userID) === friendID){
friendsCommentsArray.push(comment);
}
}
}
}
Above the for..of allows us to pause moving to the next iteration of the for loop until the Promises within the current iteration of the for loop have resolved. ie: it's sequential (note: if you tried to do this with .forEach() or .map(), your code would proceed directly to the portion after the loop before your Promises have resolved). Although, what you're after doesn't need to be sequential. We can create an array of Promises that we pass to Promise.all() which we can wait to resolve in parallel. Below I've shown a different approach of using .flatMap() to create an array of Promises that we can await in parallel with Promise.all():
const resp = await axios.post(`http://localhost:5000/router/accounts/account/following/list`, {userID: currentUserID});
const message = resp.data.message;
const promises = message.flatMap(parentArr => parentArr.map(async friendID => {
const commentsArray = await UserComment.find({userID: friendID});
return commentsArray.filter(comment => String(comment.userID) === friendID);
}));
const nestedComments = await Promise.all(promises);
const friendsCommentsArray = [...myUserComment, ...nestedComments.flat()];
instead of push try concatenation array and let me know if its work.
friendsCommentsArray = [...friendsCommentsArray , {...comment}];
// insted of
friendsCommentsArray.push(comment);
also try to use forEach instead of map() while you don't want to return a new array from your map statement.
The map method is very similar to the forEach method—it allows you to execute a function for each element of an array. But the difference is that the map method creates a new array using the return values of this function. map creates a new array by applying the callback function on each element of the source array. Since map doesn't change the source array, we can say that it’s an immutable method.
This question already has answers here:
Use async await with Array.map
(9 answers)
Closed 8 months ago.
In a map I'm asigning the results of an async/await function from a db query, but I'm doing something wrong because it doesn't work, my code:
arr.map(async(each,i)=>{
await es.getData(each.indexElastic, each[0].attributes.field_query).then(res=>auxJson.queries[i][`results${i}`] = res)
It supose to generate a json like this:
{
queries:[
{results0: res},
{results1: res},
{results2: res},
...
]
}
The response is ok, inside the map all is good, but outside the map nothing is assigned to the auxJson. I also try with Promise.all and assign everything in the then() of the Promise.all.
What is wrong?
I did this:
let promises = arr.map((each,i)=>{ return es.getData(each.indexElastic, each[0].attributes.field_query) });
let result = Promise.all(promises).then(res => {
return res.flat(1);
});
result.then(res=>res.map((answer,i)=>auxJson.queries[i][`results${i}`] = answer))
And also doesn't work...
You could do something like :
let results = await Promise.all(
arr.map(elem => queryDB(elem))
)
Maybe you should do something like this :
let promises = arr.map((each,i)=>{ return es.getData(each.indexElastic, each[0].attributes.field_query) });
let result = await Promise.all(promises).then(res => {
return res.flat(1);
});
console.log(result);
Don't forget to put this into an async function ;)
I have an Object that name is uploadedFiles. when I run this code first run console.log then run the for so I get the empty array. how can I solve the problem
let orderFilesData = [];
for (let key in uploadedFiles) {
uploadedFiles[key].map(async (file) => {
let id = file.id;
const orderFile = await this.orderFileRepository.findOne(id);
orderFile.order = order;
await this.orderFileRepository.save(orderFile);
orderFilesData.push(orderFile.fileUrl);
});
}
console.log(orderFilesData);
Since you do not return any data from the map, try using a foreach loop. Since you use an async function, what you set in orderFilesData will be an array of promises, and you'll have to await them. The simplest solution is to use Promise.all the array (console.log(Promise.all(orderFilesData)) should do what you want)
when array.map is used with async function it returns back a list of promises that is not runned. You'll have to start the process with Promise.all (or other).
Try this inside your for loop
const uploadPromises = uploadedFiles[key].map(async (file) => {
...
});
await Promise.all(uploadPromises)
I suspect that the problem is that Array.map is async, so even though each one of the calls to save has await in front of it, iterating the elements and calling the anonymous function inside the .map is done in an async manner.
Try replacing uploadedFiles[key].map with a simple for loop and I believe that it'll fix the issue.
uploadedFiles seem to be an object, where the values of the keys are arrays? So if you call uploadedFiles[key].map(...) you are creating an array of promises where each of them seems to be awaited. But as the callback of map is asynchronous, you are in fact not awaiting. The simplest would be using Promise.all() to await all promises in the array of promises resulting from map.
let orderFilesData = [];
for (let key in uploadedFiles) {
await Promise.all(uploadedFiles[key].map(async (file) => {
let id = file.id;
const orderFile = await this.orderFileRepository.findOne(id);
orderFile.order = order;
await this.orderFileRepository.save(orderFile);
orderFilesData.push(orderFile.fileUrl);
}));
}
console.log(orderFilesData);
But for this to work, make sure the surrounding function is async
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 2 years ago.
I am struggling with asynchronous loop, here is what I tried:
let array = [];
raw.forEach(async element => {
var elt = new Elt(raw);
elt.options = await this.getOptions(raw["id"]);
array.push(elt);
});
return array; // this is empty...
How can I "wait for it to be finished" so that the array isn't empty?
Thanks a lot!
Your first problem: The output array is empty because you use it before any of promises is executes. You have to await all promises before using the array.
The second problem: Promises can execute and therefore push items in (pseudo-)random order. Your output array can get shuffled.
The solution is (1) await all promises and (2) keep order of them (using Array.prototype.map):
async function foo(input) {
let output = await Promise.all(input.map(async element => {
return element * 2;
}));
return output;
}
// Call it
let input = [ 1, 2, 3, 4, ]; // This is your input array
foo(input).then(output => console.log(output));
The Promises.all is async function that takes array of promises and returns array of their results.
Array.prototype.map executes function for each item of the array.
More information:
tutorial and article about Promises on MDN
Array.prototype.map
You can use map and Promise.all for your scenario
const promises = raw.map(async element => {
var elt = new Elt(element )
elt.options = await this.getOptions(element ["id"])
return elt
})
const yourArray = await Promise.all(promises);
First await the completion of the async fetching of all of the options using map and Promise.all, and then map those IDs to a list of elements, something like this:
const options = await Promise.all(raw.map(r => this.getOptions(r.id)));
const elements = options.map(option => {
const elt = new Elt();
elt.options = option;
return elt;
});
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 4 years ago.
when I create my api in nodejs and trying to pushing mongoose return count to new created array, it's not wait for the forEach and execute json.res() and giving null response. when I use
setTimeout() then it's giving proper result.
let newcategories = [];
let service = 0;
const categories = await Category.find({}, '_id name');
categories.forEach(async (category) => {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}); /* while executing this forEach it's not wait and execute res.json..*/
console.log('result --- ',result);
console.log('out newcategories is -- ', newcategories);
res.json({status: 200, data: newcategories});
You need to use map instead of forEach, to collect the awaits and wait for them to complete. Edit: Or you can use for..of which is pretty neat (thanks other ppl)!
const categories = ['a', 'b', 'c'];
function getNextCategory(oldCategory) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(String.fromCharCode(oldCategory.charCodeAt(0)+1));
}, 1000);
});
}
async function blah() {
const categoryPromises = categories.map(getNextCategory);
const nextCategories = await Promise.all(categoryPromises);
console.log(nextCategories);
}
blah();
async function blah2() {
const nextCategories = [];
for (const category of categories) {
nextCategories.push(await getNextCategory(category));
};
console.log(nextCategories);
}
blah2();
So the problem you have is that async marked functions will return a promise per default, but that the Array.prototype.forEach method doesn't care about the result type of your callback function, it is just executing an action.
Inside your async function, it will properly await your responses and fill up your new categories, but the forEach loop on categories will be long gone.
You could either choose to convert your statements into a for .. of loop, or you could use map and then await Promise.all( mapped )
The for..of loop would be like this
for (let category of categories) {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}
the map version would look like this
await Promise.all( categories.map(async (category) => {
service = await Service.count({category: category});
newcategories.push({ count:service });
console.log('newcategories is -- ', newcategories);
}));
The second version simply works because Promise.all will only resolve once all promises have completed, and the map will return a potentially unresolved promise for each category