Home > Software design >  Pushing to array in asynchonous map
Pushing to array in asynchonous map

Time:04-14

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)
    }

CodePudding user response:

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;
}));

CodePudding user response:

.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)
    }
});

CodePudding user response:

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)
    }
});
  • Related