Catching promise errors inside another promise's callback

3662 views javascript
0

The code below runs as expected. If the charge function is invoked, the function fetches the relevant ticket object from firestore and then returns it back to the client.

If the ticket doesn't exist, the function throws a HttpsError with an error message that will be parsed by the client.

exports.charge = functions.https.onCall(data => {
  return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
    .then((snapshot) => {
      return { ticket: snapshot.data() }
    })
    .catch((err) => {
      throw new functions.https.HttpsError(
        'not-found', // code
        'The ticket wasn\'t found in the database'
      );
    });
  });

The problem comes after this. I now need to charge the user using Stripe, which is another asynchronous process that will return a Promise. The charge requires the pricing info obtained by the first async method, so this needs to be called after snapshot is retrieved.

exports.charge = functions.https.onCall(data => {
  return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
    .then((snapshot) => {
      return stripe.charges.create(charge) // have removed this variable as irrelevant for question
        .then(() => {
          return { success: true };
        })
        .catch(() => {
          throw new functions.https.HttpsError(
            'aborted', // code
            'The charge failed'
          );
        })
    })
    .catch(() => {
      throw new functions.https.HttpsError(
        'not-found', // code
        'The ticket wasn\'t found in the database'
      );
    });
  });

My problem is with catching errors in the new charge request. It seems that if the charge fails, it successfully calls the first 'aborted' catch, but then it is passed to parent catch, and the error is overridden and the app sees the 'ticket not found' error.

How can I stop this from happening? I need to catch both errors separately and throw a HttpsError for each one.

answered question

2 Answers

9

Don't nest a then inside another then for multiple items of work:

work1
.then((work1_results) => {
    return work2.then((work2_results) => {
        // this is bad
    })
})

Instead, perform all your work as a chained sequence:

work1
.then((work1_results) => {
    return work2
})
.then((work2_results) => {
    // handle the results of work2 here
})

You can store intermediate results in higher-scoped variables if you need to accumulate data between your callbacks.

posted this
3

Generally, such problems are can be handled with adding status node and then chaining with a final then block. You can try something like following

exports.charge = functions.https.onCall(data => {
  return admin.firestore().collection('tickets').doc(data.ticketId.toString()).get()
    .then((snapshot) => {
      return stripe.charges.create(charge)
        .then(() => {
          return { success: true };
        })
        .catch(() => {
             return {
                status : 'error', 
                error : new functions.https.HttpsError(
            'aborted', // code
            'The charge failed',
            { message: 'There was a problem trying to charge your card. You have NOT been charged.' }
          )};
        })
    })
    .catch(() => {
      return {
         status : 'error',
         error : new functions.https.HttpsError(
        'not-found', // code
        'The ticket wasn\'t found in the database',
        { message: 'There was a problem finding that ticket in our database. Please contact support if this problem persists. You have NOT been charged.' }
      )};
    }).then((response) => {
         if(response.status === 'error') throw response.error;
         else return response;
    });
  });

posted this

Have an answer?

JD

Please login first before posting an answer.