简体   繁体   中英

Dealing with Meteor.Error and .wrapAsync() - best methods?

Please critique the following methods of dealing with errors in Meteor:

A common problem with the Meteor.wrapAsync() method on Meteor is that it doesn't seem to return the actual error from the external API:

https://github.com/meteor/meteor/issues/2774

1. Example using wrapAsync()

I'm using a simple API that takes a US zipcode and returns the location information:

/server/methods.js

var getLocationAsync = function(zipcode, callback){

  HTTP.call("GET", "http://api.zippopotam.us/us/" + zipcode, function(error, result) {
    callback(error,result);
  });

};

var getLocationWithWrap = Meteor.wrapAsync(getLocationAsync);

Meteor.methods({
    getLocationWithWrap: function(zipcode){
        return getLocationWithWrap(zipcode);
    }
});

If you do

Meteor.call("getLocationWithWrap", "94043", function(error, result){
    if(error){
        console.log("here is the error: ", error)
    } else {
        console.log("success: ", result);
    }
})

You get a proper response. But if you pass in an invalid zipcode:

Meteor.call("getLocationWithWrap", "940", function(error, result){
    if(error){
        console.log("here is the error: ", error)
    } else {
        console.log("success: ", result);
    }
})

You get just a generic Internal server error [500] which is meaningless.

2. Example using try/catch (see code comments)

Meteor.methods({
    getLocationWithWrapTryCatch: function(zipcode){
        try {
          return getLocationWithWrap(zipcode);
        } catch(error){
          // the error object just contains a 'stack' key 
          // and the value is difficult to use
          // but the error is a 404 rather than a 500
          throw new Meteor.Error(error.stack);
        }
    }
});

Doing:

Meteor.call("getLocationWithWrapTryCatch", "940", function(error, result){
    if(error){
        console.log("here is the error: ", error)
    } else {
        console.log("success: ", result);
    }
})

results in an error object that's difficult to parse.

3. Example using @faceyspacey code

User @faceyspacey wrote a function that he called makeAsync (I've changed the name to makeSync since his code is taking an async function and allowing you to write in a synchronous style).

What it does is return an object:

{error:  error, data: data}

The value of error is the actual error returned by the external API. If the value of error is null , the value of data will be the data returned by the external API.

/server/methods.js

Meteor.makeSync = function(fn, context) {
  return function (/* arguments */) {
    var self = context || this;
    var newArgs = _.toArray(arguments);
    var callback;

    for (var i = newArgs.length - 1; i >= 0; --i) {
      var arg = newArgs[i];
      var type = typeof arg;
      if (type !== "undefined") {
        if (type === "function") {
          callback = arg;
        }
        break;
      }
    }

    if(!callback) {
      var fut = new Future();
            callback = function(error, data) {
               fut.return({error:  error, data: data});
            };

      ++i; 
    }

    newArgs[i] = Meteor.bindEnvironment(callback);
    var result = fn.apply(self, newArgs);
    return fut ? fut.wait() : result;
  };
};

var getLocationAsync = function(zipcode, callback){

  HTTP.call("GET", "http://api.zippopotam.us/us/" + zipcode, function(error, result) {
    callback(error,result);
  });

};

var getLocationWithMakeSync = Meteor.makeSync(getLocationAsync);

Meteor.methods({
    getLocationWithMakeSync: function(zipcode){
        var result = getLocationWithMakeSync(zipcode);
        if( result.error === null ){
            return result.data;
        } else {
            throw new Meteor.Error(result.error.response.statusCode, "Zipcode cannot be found.");
        }
    },
});

Now when you do

Meteor.call("getLocationWithWrap", "94043", function(error, result){
    if(error){
        console.log("here is the error: ", error)
    } else {
        console.log("success: ", result);
    }
});

You get the actual error returned from the external API or the error-less result.

I personally like the third method much better because you have the actual error object to play with from the external API. Am I missing anything here? Are there better ways of handling async errors when using external APIs?

@faceyspacey's way of doing it is the best way in my opinion. If you look at the source code for wrapAsync, it's very similar ( https://github.com/meteor/meteor/blob/832e6fe44f3635cae060415d6150c0105f2bf0f6/packages/meteor/helpers.js#L90 ). The method is also used in several external packages.

I came here having the same trouble you were: the error being thrown contains a stack trace and not the original error. It turns out that the original error is used as the prototype of the object containing the stack trace, however:

https://github.com/meteor/meteor/issues/2774#issuecomment-70710564

... all the expected properties are there, but they're on the prototype of the error object instead of on the object itself. To see this, add for (var key in e) { console.log(e, e[key]); } for (var key in e) { console.log(e, e[key]); } in the catch block.

I tested it and, sure enough, it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM