4

I am trying to make a db call insides a loop and want to add that data into one object and then send this object to user, but I am getting only a empty object at the user end. I already checked this one asynchronous response into a loop in javascript NodeJS

router.get('/collection', (req, res) => {
    const Data = {}

     Section.find({})
           .then(sections => {
             sections.map(section => {
                  let id = section._id;
                   Product.find({section: id})
                         .then(products => {
                           // console.log(products)
                            Data[section.title] = {
                                title: section.title,
                                routeName: section.title,
                                id,
                                items: products
                            }
                            console.log(Data)
                         })
                         .catch(err => {
                            return res.status(500).json(err)
                         })

              })
              return res.json(data)
           })
           .catch(err => {
               return res.status(500).json(err)
           })
})

I want the output to be like :-

{
  food : {
           items: [...]
         },
  vegetable: {
            items: [...]
             }
}

food and vegetable are keys which will be obtained from a Database Call and items in each keys are returned from a seperate call to database.

sudipt dabral
  • 1,335
  • 2
  • 9
  • 17

1 Answers1

3

return res.json(data) is executed before any of the mapped Product-promises are resolved (also there's a typo since you're returning data instead of Data). One way to do this is to map the find-promises and to use Promise.all on the mapped array. Something like:

router.get('/collection', (req, res) => {
    const Data = {}

    Section.find({})
    .then(sections => {
        const sectionProductPromises = sections.map(section => {
            let id = section._id;
            return Product.find({
                    section: id
                })
                .then(products => {                     
                    Data[section.title] = {
                        title: section.title,
                        routeName: section.title,
                        id,
                        items: products
                    }
                });

        });
        return Promise.all(sectionProductPromises);    
    })
    .then(() => {
        res.json(Data)
    })
    .catch(err => {
        res.status(500).json(err)
    });
});
eol
  • 23,236
  • 5
  • 46
  • 64
  • 2
    No, don't use `push`! Just `return` it and then use the result of the `map()` call. – Bergi May 22 '20 at 07:32
  • @Bergi: Good point, I've edited my answer. Thanks for the input! – eol May 22 '20 at 07:42
  • you can use `promise.all` directly on the `map` function like that: `Promise.all(sections.map(section => {...})` – Mahdi Hashemi May 22 '20 at 07:45
  • @MahdiHashemi: True, but I'll leave it like this as it's a bit more readable this way in my opinion. – eol May 22 '20 at 07:47
  • @MahdiHashemi: I think I can answer that - using push unnecessarily increases space-complexity as we're creating an additional array. But let's see if Bergi has another point :) – eol May 22 '20 at 07:49
  • @MahdiHashemi You could use `push` in a regular loop, or you could use `map`. (And `map` is the right tool). But you definitely shouldn't use both `map` and `push`. – Bergi May 22 '20 at 07:51