Why I am getting pending promises even after using await in the getCart() method? Is there something conceptually wrong in my code?
Please help. Thank you!
class User {
constructor(..., cart) {
//...
this.cart = cart; //{items: []}
}
getCart() {
return (async () => {
const products = this.cart.items.map(async (item) => {
const product = await Products.getProductDetail(item.productId); //this returns a promise.
product.qty = item.qty;
return product; //<pending> Promise
});
console.log(products); //<pending> Promise
return products;
})();
}
}
Here is the function call:
exports.renderCart = (req, res, next) => {
(async () => {
const products = await req.user.getCart(); //req.user is a User class object, ignore it.
console.log(products); //pending promise :(
res.render('shop/cart', { products, pageTitle: 'Cart'});
})();
};
products in your code is an array of promises. You need to wait for them all to finish using Promise.all.
The problem can be demonstrated with a bit of a mockup
var fakeWait = x => new Promise(resolve => setTimeout(() => resolve(x), 1000));
function test(){
const items = [1,2,3,4,5]
const result = items.map( async x => await fakeWait(x));
console.log(result); // list of unresolved promises
}
async function test2(){
const items = [1,2,3,4,5]
const result = await Promise.all(items.map( async x => await fakeWait(x)));
console.log(result); // list of resolved values
}
test();
test2();
In addition, your getCart method is overly complex - it does not need to be an IIFE
getCart() {
const products = this.cart.items.map(async (item) => {
const product = await Products.getProductDetail(item.productId); //this returns a promise.
product.qty = item.qty;
return product; //<pending> Promise
});
return Promise.all(products);
}
And then simply:
const products = await req.user.getCart();
Related
Below is how I generally use promises and I haven't had any issues thus far until now. The problem here is that the console.log(this.recipe) is undefined and when I console.log(JSON.stringify(recipes)) it shows me its an empty array. This leads me to believe that I am not resolving the nested promises correctly.
ngOnInit(): void {
this.recipeService.getAllRecipesByUserId().then((recipes) => {
this.allRecipes = recipes;
this.recipe = this.allRecipes[0];
console.log(this.recipe);
});
}
The getAllRecipesByUserId() function should return a promise of type Recipe[]
async getAllRecipesByUserId(): Promise<Recipe[]> {
let recipes: Recipe[] = [];
await this.userService.userRecipeIds().then((ids) => {
ids.forEach((id) => {
const q = query(this.recipeCollection, where('id', '==', id));
getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
recipes?.push(doc.data() as Recipe);
});
});
});
});
return recipes;
}
userRecipeIds():
async userRecipeIds(): Promise<string[]> {
let user: User = new User();
const q = query(
this.userCollection,
where('userId', '==', this.auth.getCurrentUser()?.uid)
);
return await getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => (user = doc.data() as User));
return user.recipes;
});
}
Am I resolving the promises correctly here ?
EDIT:
I have edited the userRecipeIds() method to the following:
async userRecipeIds(): Promise<string[]> {
const q = query(
this.userCollection,
where('userId', '==', this.auth.getCurrentUser()?.uid)
);
const docs = await getDocs(q);
const user = docs.docs[0].data() as User;
return user.recipes;
}
And then subsequently refactored the getAllRecipesByUserId() to the following:
async getAllRecipesByUserId(): Promise<Recipe[]> {
let recipes: Recipe[] = [];
const userRecipes = await this.userService.userRecipeIds();
userRecipes.forEach(async (recipeId) => {
const q = query(this.recipeCollection, where('id', '==', recipeId));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
recipes.push(doc.data() as Recipe);
});
});
return recipes;
}
I still seem to run into the same problem where the array seems to be empty upon inspection within the ngOnInit().
I don't fully understand your code, but for instance this snippet looks wrong. You are iterating over an array an assign each item to a single field called user, so at the end, after the forEach will take place, field user will have the value of the last item, so the last doc as you call it.
Maybe the whole method should look more like this:
async userRecipeIds(): Promise<Recipe[]> {
const q = query(
this.userCollection,
where('userId', '==', this.auth.getCurrentUser()?.uid)
);
const docs = await getDocs(q);
const recipes = docs.map(doc => doc.data());
return recipes;
}
in case we only want first element:
async userRecipeIds(): Promise<User> {
const q = query(
this.userCollection,
where('userId', '==', this.auth.getCurrentUser()?.uid)
);
const docs = await getDocs(q);
const user = docs[0].data();
return user;
}
Then that's how the other function can maybe look like?:
async getAllRecipesByUserId(): Promise<Recipe[]> {
const recipes: Recipe[] = await this.userService.userRecipeIds();
return recipes;
}
Answer to edited question:
When you need to resolve multiple promises at once, for instance when the promises are created from items of an array. You need to treat the results asynchronously. The foreach method doesn't care about that and will end asap, without waiting for results from your promises inside.
What you should do is use Promise.all method. Store all promises, and resolve them all, making an array of results:
async getAllRecipesByUserId(): Promise<Recipe[]> {
const userRecipes = await this.userService.userRecipeIds();
const recipieQueries = userRecipes.map(recipe => {
const q = query(...));
return getDocs(q);
})
const recipes = await Promise.all(allRecipiesPromises);
return recipes;
}
The Problem is with the uplines.push.
I always get an empty uplines array so the last part of the code doesn't run. The promises resolve later and I get the correct data. May I know how to go about doing it the correct way?
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
uplines = uplines.filter(
(v, i) => uplines.findIndex((index) => index === v) === i
); //remove duplicates
uplines.forEach((user) => {
if (user.chatId) {
sendTelegramMessage(user.chatId, saleToDisplay, currentUser.displayName);
console.log("Telegram Message Sent to " + user.displayName);
} else {
console.log(user.displayName + " has no chatId");
}
});
There are a few things that you have missed out while implementing the async call, which are explained in the inline comments in the code snippet.
A short explanation for what happened in your code is that in the line sale.rens.forEach you are passing an async function in the argument, which does not make any difference to the function forEach, it will execute it without waiting for it to complete.
Therefore in my answer I am using Promise.all to wait for all the async function calls to complete before returning the result.
// This is wrapped in an immediately executed async function because await in root is not supported here
(async () => {
const mockGetData = () => new Promise(resolve => setTimeout(resolve, 1000));
const sale = {
rens: [
{ userFid: 1 },
{ userFid: 2 },
{ userFid: 3 }
]
};
const getAllUplines = async () => {
const uplines = [];
const findUser = async (userFid) => {
// Simulating an async function call
const userDoc = await mockGetData();
console.log("User data received");
uplines.push(`User ${userFid}`);
};
const promises = [];
sale.rens.forEach(ren => { // This function in foreach does not have to be declared as async
// The function findUser is an async function, which returns a promise, so we have to keep track of all the promises returned to be used later
promises.push(findUser(ren.userFid));
});
await Promise.all(promises);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
})();
In order to get the results of getAllUplines() properly, you need to add await to all async functions called in getAllUplines().
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
await findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
await findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
I am trying to load data from firebase by calling a function in which it filters data and returns them.
When I call this function in my main function, it returns "undefined". I know the data is there (console.log(postsArray)) prints the data but I guess the return executes before data is loaded.
What am I doing wrong?
calling_Function_in_Main = async () => {
const data = await FirebaseData ();
console.log(data);
};
FirebaseData is the function that I call in my main function to load data and to return them
let postsArrays=[];
const FirebaseData = async () => {
const getViewableLink = async (link) => { //some function };
const loadData = async () => {
const database = firebase.database();
const data = database.ref();
const loadProfile = data
.child('Posts')
.orderByChild('Active')
.equalTo(true)
.once('value', function gotData(data) {
Object.values(readInfo).forEach(async (element) => {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}
});
})
.catch((error) => {
console.log(error);
}
})
.then((postsArray) => {
console.log(postsArray);
return postsArray;
});
};
await loadData();
};
export default FirebaseSwipeData;
You can't use foreach with async/await because It is not asynchronous. It is blocking.
you have 2 ways to fix this:
1- Reading in sequence: you can use for...of loop
for(const element of Object.values(readInfo)) {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}
2- Reading in parallel: you can use Promise.all
await Promise.all(Object.values(readInfo).map(async (element) => {
element.Option1Link = await getViewableLink(
preLink + element.Option1Link,
);
postsArray.push(element);
}));
Hope that solves the problem, for you
I'm using firebase - firestore. I have courses and tasks collection.
I want to get all the courses of user from courses collection and for each course to get days data from tasks collection and then save all this data in one array.
getData = () => {
var arr = []
f.auth().onAuthStateChanged(async (user) => {
db.collection("courses")
.where("uid", "==", user.uid)
.get()
.then((snapshot) => {
var a = {};
snapshot.forEach((doc) => {
let coursesData = doc.data()
let courseName = coursesData.name;
let kita = coursesData.kita;
a = { name: courseName, id: doc.data().code, k: kita };
let snapshotData = await db
.collection("tasks")
.where("uid", "==", user.uid)
.where("name", "==", courseName)
.where("kita", "==", kita)
.get();
let numActiveCourse = 0;
snapshotData.forEach((dc) => {
let taskData = dc.data()
console.log('taskData',taskData)
let days = taskData.days;
if (days > 0) {
numActiveCourse = 1;
}
});
a = { ...a, numActiveCourse };
arr.push(a);
console.log("arr2 is", arr);
});
})
.catch((e) => {
console.log("error is courses", e);
});
this.setState({data:arr})
});
};
the problem is that the arr is always empty (I guess I have asyncornize issue)
and the snapshot not await after it will finish.
I found solution.
the issue it's because I tried to make async await into forEach and it not wait to answer.
the solution is
readCourses = async()=>{
f.auth().onAuthStateChanged(async (user) => {
let loadedPosts = {};
let docSnaps = await db.collection("courses").where("uid", "==", user.uid).get();
for (let doc of docSnaps.docs){
let courseName = doc.data().name;
let kita = doc.data().kita
loadedPosts[doc.id] = {
...doc.data(),
k:kita,
id:doc.data().code
}
const taskSnap = await db
.collection("tasks")
.where("uid", "==", user.uid)
.where("name", "==", courseName)
.where("kita", "==", kita)
.get()
let numActiveCourse = 0
for(let task of taskSnap.docs){
let taskData = task.data()
if(taskData.days>0){
numActiveCourse =numActiveCourse+1
}
}
loadedPosts[doc.id].numActiveCourse = numActiveCourse
}
console.log('loadedPosts',loadedPosts)
this.setState({data:loadedPosts})
})
}
if you have any other solution I would like to see.
It's not a good idea to mix await and then as it was in your original code. There was your first mistake. Not only you didn't wait for results of forEach, but this.setState({data:arr}) was outside of then and executed even before you've reached the forEach call.
Another issue with your initial version of code is as you said - not waiting for results of forEach. But I'm not sure that you fully understand it. Because you didn't have to change your code so much (readability aside). All your had to do is:
// change db.collection("courses")...then(...) to
const snapshot = await db.collection("courses")... // only now onAuthStateChanged callback becomes async
...
// then change forEach() to map() and wait for result
const promises = snapshot.map(async (doc) => { ... })
await Promise.all(promises)
...
As for your new version: in each iteration of the for loop you await. That means that requests for every taskSnap will be executed one after another. That's bad. Especially on slow connections. Check out the snippet (I've simplified it to the bare minimum): getData with map completes in 1 second, version with for - in 4 seconds. (And you also removed catch from your new code - not a great idea.)
let i = 0
const get_courses = () => new Promise((resolve) => setTimeout(() => resolve(["a","b","c","d"]), 10))
const get_tasks = () => new Promise((resolve) => setTimeout(() => resolve(++i), 1000))
const f_auth_onAuthStateChanged = fn => fn()
const getData = () => {
const data = []
f_auth_onAuthStateChanged(async (user) => {
try {
const courses = await get_courses()
const promises = courses.map(async (course) => {
const tasks = await get_tasks()
data.push({ course, tasks })
})
await Promise.all(promises)
console.log(data) // this.setState({ data })
console.timeEnd("map")
} catch(e) { console.error(e) }
})
}
console.time("map")
getData()
const getData2 = () => {
const data = []
f_auth_onAuthStateChanged(async (user) => {
try {
const courses = await get_courses()
for (const course of courses) {
const tasks = await get_tasks()
data.push({ course, tasks })
}
console.log(data) // this.setState({ data })
console.timeEnd("for")
} catch(e) { console.error(e) }
})
}
console.time("for")
getData2()
The readCourses function from your own answer doesn't return a Promise. So formally it's not async. That won't change anything, except for a small code readability improvement. Same goes for onAuthStateChanged callback from your original code.
I am trying to learn/implement async functions, and I'm unsure why I am not able to get it to work. The 'users' in my async function are coming back undefined. However, the function that makes the database call works just fine.
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
users.on('value', function(snapshot) {
const users = []
snapshot.forEach(function(childSnapshot) {
let user = childSnapshot.val();
users.push(user)
});
return users
});
}
async function generateRandomPotentialPartner() {
try {
const users = await getUsers();
console.log(users)
} catch (error) {
console.log(error)
}
}
Use the promise version of on() instead of using the callback version and return that promise from getUsers() function.
Using the callback approach you are currently using, nothing is being returned at all since a return in a callback does not return to the outer function
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
// return the on() promise instead of using callback
return users.on('value').then(snapshot => {
const users = []
snapshot.forEach(childSnapshot => {
users.push(childSnapshot.val())
});
return users
});
}
const getUsers = () => {
const database = firebase.database()
const users = database.ref('users');
users.on('value', function(snapshot) {
const users = []
snapshot.forEach(function(childSnapshot) {
let user = childSnapshot.val();
users.push(user)
});
return new Promise((resolve,reject) => {
if(users.length == 0){
reject("There are no users"); //--- Handle rejection
}else {
resolve(users);
}
});
});
}
Your problem is that you are not returning Promises. Asynchronous Javascript is only about promises and chaining.