Having trouble wrapping a loop in a promise
I need to make an array of users before i render my page but i cant seem to figure out how to wrap my database calls
Thanks in advance.
router.get('/friends', auth.isLogged(), (req, res) => {
let friendsList = [];
User.findById(req.user._id,
{
friends: 1,
},
(err, user) => {
user.friends.map(friend => {
User.findById(friend._id).then(doc => {
friendsList.push(doc);
});
});
console.log(friendsList); <-- gets called before the loop is done
});
});
Turn the friends into an array of promises, and await a Promise.all over all of them, like this:
router.get('/friends', auth.isLogged(), (req, res) => {
let friendsList = [];
User.findById(req.user._id, {
friends: 1,
},
async (err, user) => {
const friendsList = await Promise.all(
user.friends.map(({ _id }) => User.findById(_id))
);
console.log(friendsList);
}
);
});
wrap it in Promise.all():
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all() doesn't resolve until all the promises are resolved or one i rejected.
Related
This question already has answers here:
How to return many Promises and wait for them all before doing other stuff
(6 answers)
Closed 11 months ago.
router.get("/timeline/:userId", async (req, res) => {
try {
//creating friend posts object
const friendPosts = await Promise.all(
currentUser.followings.map((friendId) => {
return Post.find({ userId: friendId });
})
);
var newFriendPosts = [];
friendPosts.map(async (friend) => {
friend.map(async (post) => {
const { profilePicture, username } = await User.findById(post.userId);
const newFriendPostsObject = {
...post.toObject(),
profilePicture,
username,
};
console.log(newFriendPostsObject);
newFriendPosts.push(newFriendPostsObject);
});
});
console.log(newFriendPosts);
// console.log(newUserPosts);
res.status(200).json(newUserPosts.concat(...newFriendPosts));
} catch (err) {
res.status(500).json(err);
}
});
So the object value is coming correct in console.log(newFriendPostsObject) but when I push that in the array newFriendPosts = []; it shows empty array in console.log(newFriendPosts);
Below is where I am getting user details from DB (DB is in MongoDB):-
const { profilePicture, username } = await User.findById(post.userId);
.map() is NOT async aware. So, it just marches on and returns an array of promises. Meanwhile, your code never waits for those promises so you're trying to use newFriendPosts BEFORE any of your .push() operations have run. .push() is working just fine - you're just trying to use the array before anything has actually gotten around to calling .push(). If you add appropriate console.log() statements, you will see that the newFriendPosts.push(...) occurs after your console.log(newFriendPosts);.
The simplest fix is to change both .map() statements to a regular for loop because a for loop IS async aware and will pause the loop for the await.
router.get("/timeline/:userId", async (req, res) => {
try {
//creating friend posts object
const friendPosts = await Promise.all(
currentUser.followings.map((friendId) => {
return Post.find({ userId: friendId });
})
);
const newFriendPosts = [];
for (let friend of friendPosts) {
for (let post of friend) {
const { profilePicture, username } = await User.findById(post.userId);
const newFriendPostsObject = {
...post.toObject(),
profilePicture,
username,
};
newFriendPosts.push(newFriendPostsObject);
}
}
console.log(newFriendPosts);
res.status(200).json(newUserPosts.concat(...newFriendPosts));
} catch (err) {
res.status(500).json(err);
}
});
One mystery in this code is that newUserPosts does not appear to be defined anywhere.
I know this question has already been asked but i just can't manage to make this right. Tried using promises but to no avail. When console logging req.user items is still an empty array. I know I should use promises but I am having trouble implementing it. Help appreciated
app.get('/cart',checkAuthenticated, async (req, res) => {
if(req.user.cart.length > 0){
req.user.items = []
req.user.cart.map(async (item) => {
var itemDescription = await productsModel.findOne({id: item.itemId})
req.user.items.push(itemDescription)
});
console.log(req.user)
}
The reason it's empty it's because you don't await for all the async functions in the map to finish. Try this:
await Promise.all(req.user.cart.map(async (item) => {
var itemDescription = await productsModel.findOne({id: item.itemId})
req.user.items.push(itemDescription)
}));
Note as #jfriend00 has commented this implementation will not guarantee the order of items in req.user.items.
Because you are already using map it's just simpler to do the following and it also guarantees the order of items:
req.user.items = await Promise.all(req.user.cart.map(async (item) => {
var itemDescription = await productsModel.findOne({id: item.itemId})
return itemDescription;
}));
.map() is not promise-aware. It doesn't pay any attention to the promise that your async callback function returns. So, as soon as you hit the await productsModel.findOne(...), that async function returns an unfulfilled promise and the .map() advances to the next iteration of the loop.
There are many different ways to solve this. If you want to use .map(), then you need to pay attention to the promise that your callback is returned like this:
app.get('/cart', checkAuthenticated, async (req, res) => {
if (req.user.cart.length > 0) {
req.user.items = await Promise.all(req.user.cart.map((item) => {
return productsModel.findOne({ id: item.itemId });
}));
console.log(req.user)
}
});
The above implementation will attempt to run all the database lookups in parallel.
A somewhat simpler implementation just uses a plain for loop and runs the database lookups one at a time:
app.get('/cart', checkAuthenticated, async (req, res) => {
if (req.user.cart.length > 0) {
req.user.items = [];
for (let item of req.user.cart) {
req.user.items.push(await productsModel.findOne({ id: item.itemId }));
}
console.log(req.user)
}
});
In your example, the array is still empty because the callback in the map function works asynchronously, hence you need to wait while the code will complete. Because map function returns array of promises, they all need to be settled using Promise.all:
app.get('/cart', checkAuthenticated, async (req, res) => {
if (req.user.cart.length > 0) {
req.user.items = []
const promises = req.user.cart.map(async (item) => {
var itemDescription = await productsModel.findOne({ id: item.itemId })
req.user.items.push(itemDescription)
});
await Promise.all(promises);
console.log(req.user)
}
});
Otherwise, you can replace map function with for loop:
app.get('/cart', checkAuthenticated, async (req, res) => {
if (req.user.cart.length > 0) {
req.user.items = []
for (const item of req.user.cart) {
var itemDescription = await productsModel.findOne({ id: item.itemId })
req.user.items.push(itemDescription)
}
console.log(req.user)
}
});
I am trying to make a request to the database (mongoDB) and save its return in a list of objects but the list is not getting filled. Here is the code
router.get('/all/:studentEmail', auth, async (req, res) => {
try {
const student = await Student.findOne({ email: req.params.studentEmail });
if (!student) {
return res.status(404).json({ msg: 'Student not found' });
}
var list = [];
student.exercises.map(async (exercise) => {
list.unshift(await Exercise.findById(exercise));
});
res.json(list);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
The database query await Exercise.findById(exercise) returns correctly the object, but res.json(list); returns empty. Do anyone know how to solve it?
The base issue is that res.json() executes way before student.exercises.map(async (exercise) => { completes. Putting await into map doesn't wait for each and every item in the async loop to process. Either use something like Promise.all() or use a for loop (other strategies can be used also). Decide which to use based on whether you can process in parallel or need to process in series. Try the following using Promise.all to execute async requests parallel using then on each Promise to execute an operation against list:
router.get("/all/:studentEmail", auth, async (req, res) => {
try {
const student = await Student.findOne({ email: req.params.studentEmail });
if (!student) {
return res.status(404).json({ msg: "Student not found" });
}
var list = [];
await Promise.all(
student.exercises.map((exercise) =>
Exercise.findById(exercise).then((result) => list.unshift(result))
)
);
res.json(list);
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
});
Also, an alternative to unshift and just return the results if they are not nested, if they are nested you can consider flat():
const list = await Promise.all(
student.exercises.map((exercise) => Exercise.findById(exercise))
);
return res.json(list);
Hopefully that helps!
I'm having a problem with promise chains that I don't know how to resolve. Summary of my code: I do a mongoose query for a specific user, fetch his CarIds and then query each car for his details and return these details via JSON response.
let carsDetails = [];
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
carIds.forEach((carId) => {
Car.findById(carId)
.then(car => {
console.log(car);
carsDetails.push(car);
})
.catch(err => { throw err; });
});
return res.status(200).json({ data: carsDetails });
})
.catch(error => {
throw error;
});
The problem is that no cars get actually pushed onto carsDetails array on the carsDetails.push(car); line, because it jumps to return statement before it manages to fill up the array. Is there a workaround that could do those queries and return a result in a form of an array, object...? I tried writing everything in async await form too with self-made async forEach statement, but it doesn't help me. Any suggestions? I already tried with Promise.all(), but didn't manage to fix the issue. Thanks!
You'll need to collect the promises of your Car.findById(carId) calls and use Promise.all() to wait for all of them before responding. You can use array.map() to map each ID to a promise from Car.findById().
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
const carPromises = carIds.map(carId => Car.findById(carId))
return Promise.all(carPromises)
})
.then(cars => {
res.status(200).json({ data: cars })
})
.catch(error => {
throw error
})
If you can have a lot of cars to find, you may want to do your query in a single request, no need to stack multiple promises :
User.findById(userId)
.then(user => {
const carIds = user.carsDetails;
// if carsDetails is not an array of objectIds do this instead :
// const carIds = user.carsDetails.map(id => mongoose.Types.ObjectId(id));
return Car.find({ _id: { $in: carIds });
})
.then(userCars => {
res.status(200).json({ data: userCars })
})
await/async is the way to go, with await/async you use regular for ... of loops instead of forEach.
async function getCarDetails() {
let carsDetails = [];
let user = await User.findById(userId);
const carIds = user.carsDetails;
for (let carID of carIds) {
let car = await Car.findById(carId)
console.log(car);
carsDetails.push(car);
}
return res.status(200).json({
data: carsDetails
});
}
Or you use Promise.all and map instead of for ... of
async function getCarDetails() {
let user = await User.findById(userId);
const carIds = user.carsDetails;
let carsDetails = await Promise.all(carIds.map(carID => Car.findById(carID)));
return res.status(200).json({
data: carsDetails
});
}
Those two solutions are slightly different. The second version with the map will send all requests to the DB at once, and then waits until they are all resolved. The first one will send the request one after another. Which one is better depends on the use-case, the second one could lead to request peeks, and might be easier be abused for DDoS.
Try to use async/await to solve this problem. It will be more readable and clean
async function getCarDetail() {
let carsDetails = []
try {
const user = await User.findById(userId)
const carIds = user.carsDetails
for (let i = 0; i < carIds.length; i++) {
const carId = carIds[i]
const car = await Car.findById(carId)
carsDetails.push(car)
}
return res.status(200).json({ data: carsDetails })
} catch(error) {
console.log(error)
}
}
I have an array of Id' and i need to get the details for each of them.
i currently have this.
const redis = require('redis');
const redisClient = redis.createClient(process.env.REDIS_PORT, process.env.REDIS_HOST);
const arrayList = [
{ id: 3444 },
{ id: 3555 },
{ id: 543666 },
{ id: 12333 },
];
async function getDetails(element) {
await redisClient.hgetall(element.id, (err, user) => {
if (err) {
console.log('Something went wrong');
// Handle Error
return err;
}
console.log('Done for User');
return user;
});
}
arrayList.forEach((element) => {
console.log('element');
await getDetails(element).then((res) => {
// Do Something with response for each element
});
});
This is the response i get right now. its not async. What am i doing wrong please.
element
element
element
element
Done for User
Done for User
Done for User
Done for User
So how things go on in async/await is, you create an async function and inside that function you await for other operations to finish. You call that async function without await OR you wrap it(func call) inside another async function.
arrayList.forEach((element) => {
console.log('element');
let returnedPromise= getDetails(element);
console.log("Promise after getDetails function", returnedPromise);
});
This code change should resolve the error.
Array.forEach() does not wait for promises to execute before moving to the next item.
You could instead use a for-loop in an async function, like so:
async function main() {
for (const element of arrayList) {
const response = await getDetails(element);
// do something with reponse for each element
}
}
main()
.then(() => /* on success */)
.catch((err) => /* on error */);