0

0) I have an HTTP trigger:

exports.checkout = functions.https.onRequest((req, res) => {

1) Update top up transactions when user buys topUp package:

admin.database().ref('topupTransaction').push(topUpObject)

2) Get the user object (with account balance)

admin.database().ref('/users/' + userID).once("value",snap=> {

3) Set the new user object (with new account balance)

admin.database().ref('/users/' + req.query.userUid).set(topUpObject);

I'm not sure how to run all these (1,2,3) sequentially and return a value to the client (0). Hitting shouldn't embed promise. There's Promise.all, but how to use it in this case.

Hitting "Avoid nesting promises." when I try to do it in this way:

exports.checkout = functions.https.onRequest((req, res) => { var nonceFromTheClient = req.body.payment_method_nonce;

  var topUpObject = {
    amount : parseInt(req.query.topUpPackage),
    date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
    user : req.query.userUid
  };

  admin.database().ref('topupTransaction').push(topUpObject)
  .then((topUpResult) => {
    return admin.database().ref('/users/' + userID).once("value");
  }).then((oldUserData)=>{
    return admin.database().ref('/users/' + req.query.userUid).set(topUpObject).then((newUserData)=>{
      return res.send(newUserData.val());
    })
      ;
  }).catch((error) => {
    // Update databse failed (top up transaction)
    console.log('Error sending message:', error);
    return res.status(500).send(error);
  });

Update

using Promise.all, but working partially with errors:

// Create 2 functions
function asyncFunction1(topUpObject){
    // Push top up object to database
    admin.database().ref('topupTransaction').push(topUpObject)
      .then((response) => {
        // Update databse successful (top up transaction)
        console.log('Top Up transaction created successfully!', topUpObject);
        // return res.redirect(303, response.ref);
        return topUpObject;
      }).catch((error) => {
        // Update databse failed (top up transaction)
        console.log('Error sending message:', error);
        return error;
      });
}

function asyncFunction2(userID,topUpObject){
    // Get the user account balance
    console.log('Current User ID: ', userID);
    var ref = admin.database().ref('users').child(userID);
    admin.database().ref('/users/' + userID).once("value",snap=> {
      // do some stuff once
      console.log('Current User Data',snap.val());
      console.log('Current User balance',snap.val().accountBalance);
      var userContents = snap.val();
      var currentBalance = userContents.accountBalance;
      var updatedBalance = currentBalance + topUpObject.amount;
      console.log('Updated Balance',updatedBalance);
      userContents.accountBalance = updatedBalance;
      /*Current User Data { 
        accountBalance: 0,
        accountCurrency: 'MYR',
        createdOn: '2018-05-02T20:42:49Z',
        phoneNumber: '+123445555555'
      }
      */
      admin.database().ref('/users/' + userID).set(userContents).then(snapshot => {
        console.log('Updated top up value! for user', topUpObject);
        return res.send(topUpObject.amount);  
      }).catch((error) => {
        // Update databse failed (top up transaction)
        console.log('Error sending message:', error);
        return error;
      });

    });
}

// app.post("/checkout", function (req, res) {
exports.checkout = functions.https.onRequest((req, res) => {
  var nonceFromTheClient = req.body.payment_method_nonce;
  // Use payment method nonce here

  // Create Transaction
  gateway.transaction.sale({ 
    amount: req.query.topUpPackage,
    paymentMethodNonce: nonceFromTheClient,
    options: {
      submitForSettlement: true
    }
  },(err, result) => { //TODO: What should we pass back here???
    if (err) {
    // If top up error (from braintree)
      console.log(err.stack);
    }else{
    // If top up is successful
      console.log('Result:',result);
      console.log('Top Up Package is: ', req.query.topUpPackage);
      var topUpObject = {
        amount : parseInt(req.query.topUpPackage),
        date : admin.database.ServerValue.TIMESTAMP, // 1525451616097
        user : req.query.userUid
      };
      return Promise.all([asyncFunction1(topUpObject), asyncFunction2(req.query.userUid,topUpObject)]); //TODO: how to pass back res() to client???
    }
    // Return the error as response
    return res.send(err);
  });
});

exports.client_token = functions.https.onRequest((req, res) => {

    // a token needs to be generated on each request
    // so we nest this inside the request handler

    // Need to write a function to return a client token,
    // and return it back by using the res.send command
    console.log('Log customerId',req.query.text);

    gateway.clientToken.generate({
      // customerId: req.query.text
    }, (err, response) => {
      // error handling for connection issues
      if (err) {
        console.log(err.stack);
      }else{
        clientToken = response.clientToken;
        console.log('Log Client token:',clientToken);
        return res.send(clientToken);
      }
      return res.send(err);
    });
    // return null;
});
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
user1872384
  • 6,886
  • 11
  • 61
  • 103
  • @Doug Stevenson, saw your youtube video https://www.youtube.com/watch?v=d9GrysWH1Lc. Like the video immensely. Do you have any videos which explains Promise.all return values in more detail? – user1872384 May 13 '18 at 14:52
  • what's the Promise.all returning? – user1872384 May 13 '18 at 15:08

1 Answers1

0

There are, in my humble opinion, a couple of things to be fine-tuned in your code, as follows. I've commented in the code.

However, note that since we don't know from where topUpObject comes from (and what it is) it is a bit difficult to be more precise. If you share more details (the full Cloud Function code and the database structure) I may be more precise.

admin.database().ref('topupTransaction').push(topUpObject)
    .then(topUpResult => {
        return admin.database().ref('/users/' + userID).once("value");
    })
    .then(oldUserData => {
         return admin.database().ref('/users/' + req.query.userUid).set(topUpObject);  
         //Set returns a non-null firebase.Promise containing void
    })
    .then(() => {  //so I don't think you can get the newUserData here
        //return res.send(newUserData.val());  <- and therefore you cannot do that here
        //But if I understand well you want to send back to the user the topUpObject, so do as follows:
        return res.status(200).send(topUpObject);  //<- note the addition of status(200) here
    })
    .catch((error) => {
        // Update databse failed (top up transaction)
        console.log('Error sending message:', error);
        return res.status(500).send(error);
});
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Hi @Renaud Tarnec, on the client side, the user is purchasing a top up package e.g. $50. In the firebase backend, it needs to do 3 things... 1st one is to create a transaction, 2nd is to get the current user data (with account balance) and add $50 to the current account balance, 3rd is to update the user data with the new balance. Do you know how to use Promise.all to achieve this? I'm using braintree sdk – user1872384 May 13 '18 at 14:03
  • Not sure you have to use Promise.all. Can you share your entire code? – Renaud Tarnec May 13 '18 at 14:05
  • Updated the code. Please help :D Not familiar with Promise.all. – user1872384 May 13 '18 at 14:13
  • I think (again, IMHO) that you mix up what is promise chaining and Promise.all. Chaining is used when one "has a sequence of asynchronous tasks to be done one after another" (see https://javascript.info/promise-chaining). Promise.all is somehow dedicated to parallel operations: it returns a promise that is fulfilled when all the items in the array are fulfilled. See https://stackoverflow.com/questions/38180080/when-to-use-promise-all. I am not sure you want to execute asyncFunction1 and asyncFunction2 in parallel. I would suggest you re-factor your entire process using chaining with then. – Renaud Tarnec May 13 '18 at 16:27
  • I would like to execute asyncFunction1 and asyncFunction2 in parallel. However in asyncFunction2, I would like to chain it for database().ref(). once() and admin.database().ref().set(). Will have a look into the link you shared and try chaining. Many thanks. – user1872384 May 14 '18 at 02:36