kaamodt
kaamodt

Reputation: 300

Why will my subsequent Meteor method calls not wait for the first one to finish when I call Meteor.setTimeout()?

I am fairly new to Meteor, fibers and futures and I am trying to understand how Meteor methods work. It is my understanding that each method call from a client would wait for a previous one to finish. This belief is mostly based on the documentation of the this.unblock() function in the Meteor docs. However, when I try setting up a simple example with a Meteor.setTimeout() call this does not seem to be a correct assumption.

methodCall.js:

if (Meteor.isClient) {
  Template.hello.events({
    'click button': function () {
      Meteor.call('test', function(error, result){
      });
    }
  });
}

if (Meteor.isServer) {
  Meteor.methods({
    test: function(){
      console.log("outside");
      Meteor.setTimeout(function(){
          console.log("inside");
          return 'done';
      }, 2000);
    }
  });
}

When triggering the 'click button' event several times the terminal output is as follows:

outside
outside
outside
outside
inside
inside
inside
inside

and not alternating between outside and inside as I would expect. I think there is a very relevant bit of information on Meteor.setTimeout() I am missing, but I could not find anything in the documentation indicating this behaviour. What am I missing and is there a way of making the Meteor method invocations from a client wait until a previous invocation is finished before starting the execution of the next?

I found this question on SO which seemed promising, but the question is more focused on blocking the possibility to call the method from the client side. Likewise, the accepted answer is not completely satisfying as it focuses on making subsequent calls skip certain code blocks of the Meteor method instead of waiting for the first invocation to finish. This very well be the answer I guess, but I really want to understand why the method call is not blocked in the first place as I feel the Meteor documentation indicates.

Upvotes: 1

Views: 968

Answers (1)

richsilv
richsilv

Reputation: 8013

The answer is that the setTimeout callback is executed outside the fiber in which the method is running. What that means is that the method actually finishes execution (returning undefined) before the setTimeout callback is ever invoked, and you get the behavior you observed.

To provide a better test (and for an example of using asynchronous functions in methods), try this:

if (Meteor.isServer) {
  var Future = Npm.require('fibers/future');

  Meteor.methods({
    test: function(){
      var fut = new Future();
      console.log("outside");
      Meteor.setTimeout(function(){
          console.log("inside");
          fut.return('done');
          return 'done';
      }, 2000);
      return fut.wait();
    }
  });
}

The return value from your setTimeout callback doesn't actually go anywhere, it just curtails that function (i.e. the callback, not the method). The way it's written above, the Future object, fut, is supplied with the return value once the callback runs, but the main method function (which is still running in its original fiber) is prevented from returning until that value has been supplied.

The upshot is that unless you unblock this method, you will get the expected output as the next method invocation won't start until the previous one has returned.

UPDATE

In general, anything with a callback will have the callback added to the event loop after the current Fiber is closed, so timeouts, HTTP calls, asynchronous DB queries - all of these fall into this category. If you want to recreate the environment of the method within the callback, you need to use Meteor.bindEnvironment otherwise you can't use any Meteor API functionality. This is an old, but very good video on the subject.

Upvotes: 3

Related Questions