Proper way to handle exceptions on a Meteor.call [solved]


#1

I have a Meteor method:

Meteor.methods({
  addChart: function (chart) {
    console.log('addChart adding new chart: Lastname:' + chart.demographics.lastName + " isSimulation: " + this.isSimulation);
    return Chart.insert(chart);
  },

and it is throwing an exception due to Collection2 schema errors, which I can see on the client and the server…so I’m trying to handle it on the client and pop an alert and log to the console like this:

Template.addChartForm.events = {
  'submit #addChart': function (e) {
    e.preventDefault();
    alert("hello test");
    var lastname = e.target.lastName.value;
    var firstname = e.target.firstName.value;
    var status = parseInt(e.target.chartStatus.value);
    var chartToInsert = {
      "demographics": {
        "lastName": lastname,
        "firstName": firstname,
        "chartStatus": status}};
    console.log('adding chart with the form');
    try {
      Meteor.call('addChart', chartToInsert);
      Router.go('/filterCharts/' + status);
      alert("successful addChart call");
    }
    catch (e) {
      console.log("problem adding chart ", e);
      alert("problem adding chart " + e);
    }
  }
}

But it doesn’t enter the catch block, even though the following error shows in the browser console:

events.js?fe697c7fa55432f16906e18f50dd2f0d97d7c8d6:91 adding chart with the form

shared.js?76ca18197849ee1c266ad4a90509e308936d325d:192 addChart adding new chart: Lastname:d isSimulation: true
2015-07-17 15:17:37.272 debug.js:41 insert failed: Error: Date of Birth is required
    at getErrorObject (http://localhost:3000/packages/aldeed_collection2.js?fdc7f0975dd1b3446ea4afd5f820cb1242a521af:409:15)
    at doValidate (http://localhost:3000/packages/aldeed_collection2.js?fdc7f0975dd1b3446ea4afd5f820cb1242a521af:392:13)
    at _.each.Mongo.Collection.(anonymous function) [as insert] (http://localhost:3000/packages/aldeed_collection2.js?fdc7f0975dd1b3446ea4afd5f820cb1242a521af:194:31)
    at Meteor.methods.addChart (http://localhost:3000/shared.js?76ca18197849ee1c266ad4a90509e308936d325d:193:18)
    at http://localhost:3000/packages/ddp.js?d1840d3ba04c65ffade261f362e26699b7509706:4269:25
    at _.extend.withValue (http://localhost:3000/packages/meteor.js?43b7958c1598803e94014f27f5f622b0bddc0aaf:955:17)
    at _.extend.apply (http://localhost:3000/packages/ddp.js?d1840d3ba04c65ffade261f362e26699b7509706:4260:54)
    at _.extend.call (http://localhost:3000/packages/ddp.js?d1840d3ba04c65ffade261f362e26699b7509706:4138:17)
    at Object.Template.addChartForm.events.submit #addChart (http://localhost:3000/client/events.js?fe697c7fa55432f16906e18f50dd2f0d97d7c8d6:93:14)
    at http://localhost:3000/packages/blaze.js?a5c324925e5f6e800a4c618d71caf2848b53bf51:3502:20

So I must be misunderstanding the flow of control when doing Meteor.call…

any help or suggestions? thanks!


#2

Have you tried doing try/catch within your Meteor method itself?

PS: Your syntax for the event map is deprecated usage. It should be:

Meteor.events({
  'submit #addChart': function (e) { ... }
});

#3

Yes, I tried it there and it worked, however, I tried to do the alert, and then it complained when it was running on the server… because the Meteor.method code is shared on client and server… I read once that was the way to go, so it executes the “insert” both on the client and the server. … but then you can’t code client specific stuff like “alert” unless you then wrap it in the isClient conditional… so I’m still confused as to the best practice here for catching and reporting errors to end users.


#4

?? the meteor docs reference the way I am doing it:
Attaching events to templates

Event listeners are added to templates in much the same way as helpers are: by calling Template.templateName.events(…) with a dictionary.


#5

Look again carefully at the syntax. It’s Template.x.events({, not Template.x.events = {.


#6

AHHH… ok, I see :slight_smile:


#7

SOLVED!.. the issue was that Meteor.call does not propagate exceptions, found this in the docs:

Methods called on the client run asynchronously, so you need to pass a
callback in order to observe the result of the call. The callback will be
called with two arguments, error and result. The error argument will
be null unless an exception was thrown. When an exception is thrown, the
error argument is a Meteor.Error instance and the result argument is
undefined.

Solution was:

Template.addChartForm.events({
  'submit #addChart': function (e) {
    e.preventDefault();
    var lastname = e.target.lastName.value;
    var firstname = e.target.firstName.value;
    var status = parseInt(e.target.chartStatus.value);
    var chartToInsert = {
      "demographics": {
        "lastName": lastname,
        "firstName": firstname,
        "chartStatus": status}};
    console.log('adding chart with the form');
    Meteor.call('addChart', chartToInsert, function (err, result) {
      if (!err) {
        console.log("meteor call to add chart was good");
        Router.go('/filterCharts/' + status);
      } else {
        alert("meteor call was bad " + err);
        console.log("meteor call was bad", err);
      }
    });
  }
});

#8

Hi !

I’ve the same problem.
You solution is right, but, in this way, you can catch only errors from the server-side… So, if you print error on template, you will have to wait for server response, whereas collection2 can run on client side (if you check your browser console, you could see an error on client side which is not cached by your code).

That’s my problem, I’m using a shared code between client and server on Meteor.methods, to have benefit of latency compensation, but error handling are different in each system, and I don’t know how to resolve this…

If someone can help me,
Many Thanks,
Yannis.


#9

After thinking about this problem that really cuts the benefits of latency compensation in half (anytime the client fails). If the same failure process was taken on both client and server, we could still have the benefit of latency compensation. Basically, make the callback functionality for both the async server and sync client calls the same. This will take a little namespace management to make the callback function available in both places, but that’s not hard.

Meteor.methods({
    doSomething: function() {
        try {
    		/*
                    main function
                */
        } catch (err) {
            if (isServer) throw err;
              callback();    // callback used for client side meteor method exceptions
            }
        }
});

Meteor.call('doSomething', function(err, res){
    // callback used for server side meteor method exceptions
    if (err) callback();
});

A post by @elvismercado gave me the idea of creating a wrapper meteor method that could standardize this approach across an entire app.


#10

Or, you could skip the collection2 approach, do your own checks within the method, and wrap a reusable method that throws on the server and does something else on the client.