0

I am working on a route in express and I am not sure why my "purchasedCards" array is empty at the end of these database calls. I first collect all the information I need from a few DB Queries then I put it into the context to send to my front end. All the DB calls are working fine... Even the cardUpdator object. The only problem I have is getting the object pushed to the array...

I am new to using the postgres database /SQL. Any advice is certainly appreciated.

    //User Account
router.get('/account', isLoggedIn, (req, res) => {
  //Get User Info - First get the user info
  db.query(`SELECT * FROM users WHERE userid = '${res.locals.user.id}';`)
  .then(user => {
    //Get user Purchases
    db.query(`SELECT * FROM purchases WHERE newowner = '${res.locals.user.id}';`)
    .then(purchases => {
      let purchasedCards = []
      //Get Card Info for Each Purchase
      purchases.forEach( purchasedCard => {
        //Get the card from user_cards table for standard card info.
        db.query(`SELECT * FROM user_cards WHERE id = '${purchasedCard.card}';`)
        .then( card => {
          //Get Old Owner Info
          db.query(`SELECT * FROM users WHERE userid = '${purchasedCard.oldowner}';`)
          .then(oldOwner => {
            let cardUpdator = {
              cardName: card[0].name,
              cardGame: card[0].game,
              cardOldOwner: oldOwner[0].screen_name,
              cardPID: purchasedCard.purchaseid,
              cardTotal: purchasedCard.total,
              cardId: purchasedCard.card,
            }
            purchasedCards.push(cardUpdator)
          })
        })
      })
      let context = {
        name: user[0].screen_name,
        email: user[0].email,
        purchases: purchasedCards,
      }
      res.render('MyAccount.hbs', context) 
    })
  })
})
Nick McLean
  • 601
  • 1
  • 9
  • 23
  • because you are ignoring the fact it is an asynchronous call. The query is using a promise, you do not wait for all the promises to get done. – epascarello Aug 20 '20 at 19:57
  • Hmm... So would I put the context object and the res.render() into another .then() so that it waits for everything? – Nick McLean Aug 20 '20 at 20:07
  • Since it is multiple you are looking at Promise.All or asynch/await – epascarello Aug 20 '20 at 20:08
  • 1
    Does this answer your question? [Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference](https://stackoverflow.com/questions/23667086/why-is-my-variable-unaltered-after-i-modify-it-inside-of-a-function-asynchron) – ponury-kostek Aug 20 '20 at 20:09

2 Answers2

0

You should wait for the asynchronous operation to finish using promises. To make your code more tidy, use async await.

//User Account
router.get('/account', isLoggedIn, (req, res) => {
  //Get User Info - First get the user info
  db.query(`SELECT * FROM users WHERE userid = '${res.locals.user.id}';`)
  .then(user => {
    //Get user Purchases
    db.query(`SELECT * FROM purchases WHERE newowner = '${res.locals.user.id}';`)
    .then(purchases => {
      //Get Card Info for Each Purchase
      let getCardPromises = purchases.map(purchasedCard => {
        //Get the card from user_cards table for standard card info.
        return db.query(`SELECT * FROM user_cards WHERE id = '${purchasedCard.card}';`)
        .then( card => {
          //Get Old Owner Info
          return db.query(`SELECT * FROM users WHERE userid = '${purchasedCard.oldowner}';`)
          .then(oldOwner => {
            let cardUpdator = {
              cardName: card[0].name,
              cardGame: card[0].game,
              cardOldOwner: oldOwner[0].screen_name,
              cardPID: purchasedCard.purchaseid,
              cardTotal: purchasedCard.total,
              cardId: purchasedCard.card,
            }
            return cardUpdator;
          })
        })
      })
      Promise.all(getCardPromises).then(getOwnerPromises => {
        Promise.all(getOwnerPromises).then(purchasedCards => {
          let context = {
            name: user[0].screen_name,
            email: user[0].email,
            purchases: purchasedCards,
          }
          res.render('MyAccount.hbs', context) 
        })
      })
    })
  })
})

Edit, I add the async/await version, definitely make the code cleaner

//User Account
router.get('/account', isLoggedIn, async (req, res) => {
    //Get User Info - First get the user info
    let user = await db.query(`SELECT * FROM users WHERE userid = '${res.locals.user.id}';`)
    let purchases = await db.query(`SELECT * FROM purchases WHERE newowner = '${res.locals.user.id}';`)
    //Get Card Info for Each Purchase
    let purchasedCardsPromises = purchases.map(async (purchasedCard) => {
        //Get the card from user_cards table for standard card info.
        let [card, oldOwner] = await Promise.all([  db.query(`SELECT * FROM user_cards WHERE id = '${purchasedCard.card}';`),
                                                    db.query(`SELECT * FROM users WHERE userid = '${purchasedCard.oldowner}';`)])
        let cardUpdator = {
            cardName: card[0].name,
            cardGame: card[0].game,
            cardOldOwner: oldOwner[0].screen_name,
            cardPID: purchasedCard.purchaseid,
            cardTotal: purchasedCard.total,
            cardId: purchasedCard.card,
        }
        return cardUpdator;
    })
    let purchasedCards = await Promise.all(purchasedCardsPromises)
    let context = {
    name: user[0].screen_name,
    email: user[0].email,
    purchases: purchasedCards,
    }
    res.render('MyAccount.hbs', context)
})
Helix
  • 187
  • 1
  • 6
  • Wow! Thanks for writing this out. The .map() is awesome! I'll start using that... I am confused on the Promise.all section though - So the first Promise.all() waits for the getCardPromises to resolve... right? Then it returns 'getOwnerPromises'. Can you explain why the second Promise.all is there? – Nick McLean Aug 20 '20 at 20:24
  • As you have pointed out, first promise waits for the `getCardPromises` to resolve. When it does resolve, it return another promise, that is the `getOwnerPromises`. Therefore you need to wait for `getOwnerPromises` to resolve too, hence the need for the second promise. If you just send the getOwnerPromises, then you'll end up at the original problem. – Helix Aug 20 '20 at 20:27
  • Wow! The async await one makes a lot more sense as I had been using that before. I guess I was just going with the .then() approach because the rest of the project was written that way. -- I see so the getOwnerPromises is the promise that is sent back from the original Promise.all. I guess I got confused cause I saw that getCardPromises was defined above but getOwnerPromises was not. -- Thanks for taking time to get me thinking about this! I really appreciate the lesson. – Nick McLean Aug 20 '20 at 20:45
  • Hmm the async await version above doesnt work... I'ma try to figure it out! The cardUpdator works. I console.log(cardUpdator) and it works. Seems like the same issue is happening where I need to await this thing to get done for the context object. console.log(context) says promise pending for each card that is in the array. – Nick McLean Aug 20 '20 at 20:52
  • I think I missed the part where `purchases.map` actually still return promises, so you need to wait for it resolve too. Let me know if there is any problem. – Helix Aug 20 '20 at 21:01
  • Got it! I was thinking the same thing once I saw that it was an array of promises! Thanks so much for taking your time to help! – Nick McLean Aug 20 '20 at 21:03
  • Ur welcome! Play with promise and async/await a few more times and you'll surely get more comfortable already. – Helix Aug 20 '20 at 21:06
0

it may be better to use async/await to avoid multiple callbacks. However the reason for the unexpected behaviour is because you are trying to access purchasedCards before it has been populated. This is because JavaScript is asynchronous in native, hence at the point you are accessing purchasedCards the promise is yet to resolve.

I have a suggestion you can follow if you so choose, i used async/await. Hopefully, it helps.

async function getPurchasedCards(purchases) {
  const purchasedCards = [];
  return purchases.forEach((purchasedCard) => {
    //Get the card from user_cards table for standard card info.
    db.query(
      `SELECT * FROM user_cards WHERE id = '${purchasedCard.card}';`,
    ).then((card) => {
      //Get Old Owner Info
      db.query(
        `SELECT * FROM users WHERE userid = '${purchasedCard.oldowner}';`,
      ).then((oldOwner) => {
        let cardUpdator = {
          cardName: card[0].name,
          cardGame: card[0].game,
          cardOldOwner: oldOwner[0].screen_name,
          cardPID: purchasedCard.purchaseid,
          cardTotal: purchasedCard.total,
          cardId: purchasedCard.card,
        };
        purchasedCards.push(cardUpdator);
        if (purchasedCards.length === purchases.length) return purchasedCards;
      });
    });
  });
}

async function getPurchasedCards(purchases) {
  const purchasedCards = [];
  return purchases.forEach((purchasedCard) => {
    //Get the card from user_cards table for standard card info.
    db.query(
      `SELECT * FROM user_cards WHERE id = '${purchasedCard.card}';`,
    ).then((card) => {
      //Get Old Owner Info
      db.query(
        `SELECT * FROM users WHERE userid = '${purchasedCard.oldowner}';`,
      ).then((oldOwner) => {
        let cardUpdator = {
          cardName: card[0].name,
          cardGame: card[0].game,
          cardOldOwner: oldOwner[0].screen_name,
          cardPID: purchasedCard.purchaseid,
          cardTotal: purchasedCard.total,
          cardId: purchasedCard.card,
        };
        purchasedCards.push(cardUpdator);
        if (purchasedCards.length === purchases.length) return purchasedCards;
      });
    });
  });
}
samuel
  • 23
  • 1
  • 8