Search code examples
meteorstripe-paymentsnode-fibers

cannot catch Meteor.Error in client when error in server inside Meteor.bindEnvironment


I am not able to get the error in client's Meteor.call error callback when in the server code, an error occurs inside Meteor.bindEnvironment. Below is example code to replicate

In the server

Meteor.methods({
  customMethod: function(arg1, arg2){
      Stripe.customers.create({
        email: "[email protected],
        description: "blah blah",
        source: token,
        metadata: {
          planId: planId,
          quantity: n
        },
        plan: planId,
        quantity: n
      }, Meteor.bindEnvironment(function (err, customer) {
        if(err){
          console.log("error", err);
          // TODO cannot catch this error on the client
          throw new Meteor.Error(err.rawType, err.message)
        }
      }))
    }
})

In the client inside a Meteor event,

Meteor.call('customMethod', arg1, arg2, function (err, resp) {
 if(err){
   Session.set('some-error', err)
 }
 if(resp){
   // TODO cannot catch errors throwing from the server
   // when inside Meteor.bindEnvironment 
   Session.set('some-success', true)
 }
});

The session variables are never set. Any help would be great. Thanks!


Solution

  • The second argument to Meteor.bindEnvironment is an error-handler that gets called whenever an exception is thrown within the callback you supplied as the first argument. So you can do something like this to get the error passed back to the client:

    Meteor.bindEnvironment(function (err, customer) {
      if (err) throw err
      ...
    }, function (err) {
      if (err) throw new Meteor.Error(err.message)
    })
    

    UPDATE

    Apologies, that was a bit hasty. The problem is the fact that your error (and potentially results) are coming from an asynchronous callback, so your method function will have finished executing, and implicitly returned undefined (which gets passed to the client as null) by the time the callback does anything.

    Historically, you'd resolve this with a future, but now we have promises, which are better:

    Meteor.methods({
      customMethod (arg1, arg2) {
        return new Promise((resolve, reject) => {
          Stripe.customers.create({
            email: "[email protected],
            ...
          }, Meteor.bindEnvironment(function (err, customer) {
            if(err){
              reject(err)
            }
            resolve(customer)
          })).catch(e => { throw new Meteor.Error(e) })
      }
    })
    

    Meteor methods are clever enough to wait for promises to resolve or reject and return the result (or error) via DDP. You still need to catch the error and formally throw it, but your method call will wait for you to do so.