Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding Callback Hell with Multiple Meteor Method calls on Client

I have multiple Meteor.calls, where each methods depends on the response of another Meteor method.

Client

Meteor.call('methodOne', function(err, resOne){
    if(!err){
        Meteor.call('methodTwo', resOne, function(err, resTwo){
            if(!err){
                Meteor.call('methodThree', resTwo, function(err, resThree){
                    if(err){
                        console.log(err);
                    }
                })
            }
        });
    }
});

From Meteor's documentation I know

"Methods called on the client run asynchronously, so you need to pass a callback in order to observe the result of the call."

I know I can create yet another Meteor Method on the server to execute methods 'methodOne', 'MethodTwo', 'MethodThree' wrapped using Meteor.async, or sequentially without the callback all together. But I am worried this path will cause my meteor methods to get bloated and entangled, leading to spaghetti code. I would rather keep each Meteor method simple with one job to do and find a more elegant way of chaining the calls on the client. Any ideas, is there any way to use Promises on the client?

like image 829
Robin Avatar asked Feb 20 '15 16:02

Robin


3 Answers

Since the other answer suggests RSVP this answer will suggest Bluebird which is actually the fastest promise library when running real benchmarks. Rather than a micro benchmark that does not really measure anything meaningful. Anyway, I'm not picking it for performance, I'm picking it here because it's also the easiest to use and the one with the best debuggability.

Unlike the other answer, this one also does not suppress errors and the cost of making the function return a promise is marginal since no promise constructor is called.

var call = Promise.promisify(Meteor.call, Meteor);

var calls = call("methodOne").
            then(call.bind(Meteor, "methodTwo")).
            then(call.bind(Meteor, "methodThree"));

calls.then(function(resThree){
    console.log("Got Response!", resThree);
}).catch(function(err){
    console.log("Got Error", err); 
});
like image 88
Benjamin Gruenbaum Avatar answered Oct 22 '22 20:10

Benjamin Gruenbaum


Your approach on the client results in a lot more round trips between the server and the browser. I know you indicated that you were worried about spaghetti code on the server and I don't have visibility into your application as you do, but just going by the example you provide, it seems like an ideal place to wrap all three calls on the server and make only one call from the client, IMHO.

like image 3
Larry Maccherone Avatar answered Oct 22 '22 18:10

Larry Maccherone


EDIT: You're probably better off looking at @Benjamin Gruenbaum answer, which not only results in better performance, but also provides much more concise code.

Promises - yes there is.

I like RSVP very much, why? Simply because it's the fastest one. (quick benchmark: jsperf ).

Here's quick re-write of your code:

var promise = new RSVP.Promise(function(fulfill, reject) {
  Meteor.call('methodOne', '', function(err, resOne) {
    if (!err) {
      return reject(err);
    }
    fulfill(resOne);
  });
});

promise.then(function(resOne) {
  return new RSVP.Promise(function(fulfill, reject) {
    Meteor.call('methodTwo', resOne, function(err, resTwo) {
      if (err) {
        return reject(err);
      }
      fulfill(resTwo);
    });
  });
}).then(function(resTwo) {
  return new RSVP.Promise(function(fulfill, reject) {
    Meteor.call('methodTwo', resTwo, function(err, resThree) {
      if (err) {
        reject(err);
      }
      fulfill(resThree);
    });
  });
}).then(function(resThree) {
  // resThree is available - continue as you like
  console.log(resThree);
}).catch(function(err) {
  console.log(err);
});

That's the way to prevent "the ever rightward drift" of your code.

Promises are cool, use them.

like image 3
bardzusny Avatar answered Oct 22 '22 20:10

bardzusny