4

I'm trying to work on a MERN full stack app where the frontend sends an api call to "/createDeckOfCards" to my nodejs backend. The goal is to click on a button to create a new deck of cards, then return the list of created cards. The parameter numOfCards is sent with this call as well.

So on my nodeJS backend, I have the "/createDeckOfCards" endpoint where I use .map() to iteratively create each card and then save to mongoDB like so:

const allCardsArray = [...Array(req.body.numOfCards).keys()]

allCardsArray.map(async (i)=>{
  const eachCard = new eachCardModel({
    eachCardTitle: String(i)
  })
  
  eachCard.save((err, doc) => {
    if (err) return res.status(400).json({ errMsg: "Something went wrong" });
    else{
      CardDeckModel.findOneAndUpdate(
        {_id: req.cardDeckCreated._id},
        {$push:{allCards: doc}},
        function(error, success){
          if (error){
            console.log(error)
            return res.status(400).json({ errMsg: "Something went wrong" });
          } else {
            console.log("success")
          }
        }
      )
    }
  });
})

console.log("COMPLETED") //DOES NOT EXECUTE LAST!

//THIS RETURNS BEFORE THE .map() is done
res.status(200).json({ 
  createdCardDeckID: req.cardDeckCreated._id 
})
})

After that, I have a second endpoint "/returnAllCardsInDeck" where I pass in the ID of the cardDeck like so:

CardDeckModel.findOne({_id: req.body.createdCardDeckID}).populate({path: 'allCards', options: { sort: "eachCardTitle" } }).exec((err, cardDeck) => {
    if (err) return res.status(400).json({ errMsg: "Something went wrong" });
    else {
      res.status(200).json({
        CardDeck: cardDeck
      })
    }
  })

The problem is, CardDeck returns before the allCardsArray.map() is completed. This would be a problem because I want the user to see ALL cards in the deck once the deck is created. But because the "/returnAllCardsInDeck" executes before the "/createDeckOfCards", it returns be an undefined object.

Also, am I doing this right? Esp with regards to the first part ("/createDeckOfCards").

Reine_Ran_
  • 662
  • 7
  • 25
  • 1
    Can you use async/await??or you want to use just callback?? – Mohammad Yaser Ahmadi Feb 12 '21 at 14:21
  • 1
    Possibly send back [409](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409) indicating the deck isn't ready yet? Do you want the second request to be held until the deck is constructed or do you want to close out the second request and make them make another? – zero298 Feb 12 '21 at 14:56
  • 1
    @MohammadYaserAhmadi thanks for replying! But do you mean making the function then adding await like the answer below? Coz I've tried it and it didn't work :/ – Reine_Ran_ Feb 12 '21 at 16:02
  • 1
    @zero298 Thanks for suggesting that! I don't exactly know which is the right way but the second request can only run after the deck is constructed since it's trying to return all cards in the deck (which im iteratively trying to create in my map function). – Reine_Ran_ Feb 12 '21 at 16:04

2 Answers2

2

try this, you can't do async call like this with map. There are patterns to solve this issue. Promise.all is one of them.

const allCardsArray = [...Array(req.body.numOfCards).keys()]

await Promise.all(
    allCardsArray.map((i)=>{
        const eachCard = new eachCardModel({
          eachCardTitle: String(i)
        })

        return eachCard.save()
            .then(
                () => 
                    CardDeckModel.findOneAndUpdate({_id: req.cardDeckCreated._id}, {$push:{allCards: doc}})
                    .then(() => console.log("success"))
                    .catch((error) => console.log(error) || res.status(400).json({ errMsg: "Something went wrong" });)
        ).catch(() => res.status(400).json({ errMsg: "Something went wrong" }))
      })
)

    console.log("COMPLETED") //DOES NOT EXECUTE LAST!

    //THIS RETURNS BEFORE THE .map() is done
    res.status(200).json({
        createdCardDeckID: req.cardDeckCreated._id
    })
})
Tan
  • 342
  • 3
  • 11
  • 1
    Thanks for helping! But I just tried that but somehow it didn't work...it just didn't wait even after I did what you suggested.. – Reine_Ran_ Feb 12 '21 at 16:03
  • 1
    try to return eachCard.save – Tan Feb 12 '21 at 16:25
  • 1
    ohh sorry you pass callback to save and findOneAndUpdate so they don't return Promise. I will refactor this for you give me a sec – Tan Feb 12 '21 at 16:40
1

you can use for of with async/await instead of map like this

const allCardsArray = [...Array(req.body.numOfCards).keys()];

for (let i of allCardsArray) {
  const eachCard = new eachCardModel({
    eachCardTitle: String(i),
  });
  try {
    let doc = await eachCard.save();
    await CardDeckModel.findOneAndUpdate(
      { _id: req.cardDeckCreated._id },
      { $push: { allCards: doc } }
    );
  } catch (error) {
    return res.status(400).json({ errMsg: "Something went wrong" });
  }
}
console.log("success");
res.status(200).json({
  createdCardDeckID: req.cardDeckCreated._id,
});
Mohammad Yaser Ahmadi
  • 4,664
  • 3
  • 17
  • 39