Using async/await with a for in loop - javascript

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

Related

Empty Array returned and not being populated when pushing objects

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.

Map array of objects and change one property with a function that calls an API. I keep getting promise pending

I have to loop through an array of objects and modify one single property in each object. I modify this property with a function that connects to the Twitter API. My problem is that I must be using async and await wrongly because I am getting a promise pending.
This is my code:
getProfile:(req,res)=>{
try {
const userId=req.params.id
const profile=db.query('SELECT * FROM profiles WHERE user_id=?',
[userId],async (err,result)=>{
if(err) return res.status(404).send(err)
const profiles= await result.map( obj=>{
const container={}
container['name']=obj.profile_name
container['desc']=obj.profile_desc
container['twitter']= connectTwitt.getTwitt(obj.twitt)//calls to api
return container
})
console.log(profiles)// promise pending
res.send(profiles)
This is the structure of the array of object that I am mapping:
[
{profile_name:`Elon Musk`, profile_desc:'enterpreneur',twitt:636465}
]
Yes, you are using the async/await syntax a little bit incorrectly.
Right now, you are calling await on the Array.map() method. However, that method is not promise-based.
Instead, you have to add the await keyword to the getTwitt() method, and await for all promises to complete.
With those changes, it should look like below.
const profiles = await Promise.all(result.map(async (obj) => { // This line has been modified
const container = {};
container["name"] = obj.profile_name;
container["desc"] = obj.profile_desc;
container["twitter"] = await connectTwitt.getTwitt(obj.twitt); // This line has been modified.
return container;
}));
Hopefully this helps with your <pending> issue!

Using Async/Await with Array.map

So I know that async/await will not work with map. I am just returning my request and using Promise.all() to execute them. My question is that I have other promises inside and was wondering if the Promise.all() will also execute those inside the map in the correct order.
Here is the code:
const productImageIds = Object.keys(data.webImages)
for(let i = productImageIds.length; i--;){
const productId = productImageIds[i]
const images = data.webImages[productId]
const requests = images.map(async (image, i) => {
const name = `${productId}_${i}.${image.split(`.`).pop()}`
const imageStream = await downloadImage(image, name) // IS THIS WORKING CORRECTLY WITH USING PROMISE.ALL() ??
const res = sanityRequest({
...sanityConfig,
type: `images`,
endpoint: `assets`,
contentType: `image/jpeg`,
body: imageStream,
params: `?filename=${name}`,
})
await unlinkSync(name) // IS THIS WORKING CORRECTLY WITH USING PROMISE.ALL() ??
return res
})
const uploadedImages = await Promise.all(requests)
}
My question is that I have other promises inside and was wondering if the Promise.all() will also execute those inside the map in the correct order.
No.
Promise.all() will create a promise which resolves when all the promises passed to it resolve. It has no influence over what order those promises resolve in (and couldn't because they will have started running before Promise.all is invoked).
If you want to deal with each value of images in sequence (rather than in parallel) then use a regular for loop.

How to use async call inside forEach when using firebase calls

the question that I have is that I can't figure out how to make this code work properly using Firestore (not sure if this is irrelevant).
The actual code is the following:
prestamoItems() {
var myarray = [];
var myobject = {};
//here comes the first async method (works OK)
fb.prestamosCollection
.orderBy("fechaPrestamo", "desc")
.get()
.then(val => {
if (!val.empty) {
//here comes forEach
val.docs.forEach(doc => {
myobject = doc.data();
myobject.id = doc.id;
console.log("The doc id is " +myobject.id)
//here comes second async call inside the forEach loop, but it doesnt wait for this
//to be finished, and immediately goes to the other step
fb.equiposCollection.doc(myobject.id).get().then(eqp => {
console.log("The doc id from the other collection is " +eqp.id)
})
myarray.push(myobject)
console.log("myobject pushed to myarray")
});
}
});
}
Please note that I'm calling an async method inside a forEach loop that comes from another async method. In every variation of the code, the output that I'm getting (the console logs) are the following:
11:13:14.999 Prestamos.vue?18d2:71 The doc id is 1yTCUKwBvlopXX2suvVu
11:13:14.999 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is Z5TE15Fj3HFrn1zvceGe
11:13:15.000 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is JNN9aN65XE1tUTmlzkoJ
11:13:15.000 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.000 Prestamos.vue?18d2:71 The doc id is NF2hHCpM8leZezHbmnJx
11:13:15.001 Prestamos.vue?18d2:78 myobject pushed to myarray
11:13:15.364 Prestamos.vue?18d2:74 The doc id from the other collection is 1yTCUKwBvlopXX2suvVu
11:13:15.368 Prestamos.vue?18d2:74 The doc id from the other collection is Z5TE15Fj3HFrn1zvceGe
11:13:15.374 Prestamos.vue?18d2:74 The doc id from the other collection is JNN9aN65XE1tUTmlzkoJ
11:13:15.379 Prestamos.vue?18d2:74 The doc id from the other collection is NF2hHCpM8leZezHbmnJx
So, the forEach loop is not waiting to the async function inside it (which actually is the expected behavior, AFAIK).
The question is how can I make it wait for the inner call to be finished before adding the obect to the array? Thanks in advance.
either you nest code, which depends on previous results into then() callbacks or you wrap the loop (forEach does not support async) in async block to make use of await inside. eg.:
fb.prestamosCollection
.orderBy("fechaPrestamo", "desc")
.get()
.then(val => {
if (!val.empty) {
// wrap loop in async function call iife so we can use await inside
(async () => {
for (var i = 0; i < val.docs.length; i++) {
const doc = val.docs[i];
myobject = doc.data();
myobject.id = doc.id;
// this will be synchronous now
let eqp = await fb.equiposCollection.doc(myobject.id).get();
console.log(eqp.id);
myarray.push(myobject)
}
})();
}
});
The root of the problem is that you're trying to turn an asychronous operation (waiting for Firestore to return values) into a synchronous one. This isn't really possible in a meaningful way in JavaScript without causing lots of issues!
You'll need to populate your array inside of the .then() callback and return the promise as a result of the function. Any caller that calls your prestamoItems() function will also have to use .then() callbacks to access the underlying myarray value:
const _ = {
async prestamoItems() {
const val = await fb.prestamosCollection.orderBy("fechaPrestamo", "desc").get();
if (val.empty) {
return myarray
}
// Promise.all() will take a list of promises and will return their results once they have all finished.
return await Promise.all(
// Array.prototype.map() will take an existing array and, for each item, call the given function and return a new array with the return value of each function in that array.
// This is functionally equivalent to making a new array and push()ing to it, but it reads a lot nicer!
val.docs.map(async doc => {
const myobject = doc.data();
const eqp = await fp.equiposCollection.doc(myobject.id).get()
// I presume you want to do something with eqp here
return myobject
})
);
}
}
The above code sample uses Array.prototype.map() to do away with myarray as it's not necessary.
A caller would have to use this code like this:
_.prestamoItems().then((myarray) => {
...
})
Promises are a way of saying that a value may be avaliable at some point in the future. Because of this, you have to make sure that any interaction you have with a promise is written in such a way that assumes the value is not avaliable immediately. The easiest way to do this is by using async/await and ensuring that you return promise objects.
just move the push inside then like this
fb.equiposCollection.doc(myobject.id).get().then(eqp => {
console.log("The doc id from the other collection is " +eqp.id)
myarray.push(myobject)
console.log("myobject pushed to myarray")
})

ES7 Getting result from an array of promises using await generator

Given an array of promises, what's the idiomatic way to get the results in ES7?
Here's what I want to do:
async function getImports() {
let imports = [System.import('./package1.js'), System.import('./package2.js')];
let promises = await* imports;
let results = [];
await promises.forEach(val => val.then(data => results.push(data))); //seems hacky
console.log(results); // array of 2 resolved imports
}
The result is correct, but I'm still doing a forEach and a then to turn the resolved promises into results. This just doesn't seem right to me. Is there a cleaner way?
As mentioned in the issue you filed, the core issue is that await* is no longer a thing and has been removed. Unfortunately, it was not properly throwing a syntax error in Babel 6 and was essentially being treated like a normal await.
You'll need to explicitly
let [p1, p2] = await Promise.all([
System.import('./package1.js'), System.import('./package2.js')]);
I cannot believe it actually works, forEach does return undefined which you cannot await. If you need a loop, use map to get an array of (promised) results.
In your case, you seem to be looking for a simple
async function getImports() {
let promises = [System.import('./package1.js'), System.import('./package2.js')];
let results = await Promise.all(promises)
console.log(results);
}
One way to do it this ....
async function abc() {
let p1 = getReviews();
let p2 = getMenu();
let [reviews, menu] = await results(p1, p2);
}
function results(...rest) {
return Promise.all(rest).catch(err => console.log(err));
}

Categories

Resources