Meteor - Under The Hood

meteor

What technology is Meteor based on?

Meteor is based on NodeJS.

What are the characteristics of Meteor?

Meteor is based on NodeJS so it has two characteristics:

  1. Node.js is event driven
  2. Node.js use nonblocking I/O

Do we have to worry about the callback-hell problem when using Meteor?

Most of the time we do not really have to worry about the callback-hell problem especially on the server-side. On the client-side, we still use callbacks, and there we may run into this issue, but we may be able to manage it somehow. On the server-side where the logic is usually more complex, Meteor take care of this for us.

How does Meteor achieve the synchronous style while Node use asynchronous style? How does Meteor avoid the callback-hell issue when it comes to making database calls, and non-blocking I/O operations?

Meteor is based on NodeJS so it has two characteristics:

  1. Node.js is event driven
  2. Node.js use nonblocking I/O

Meteor take care of this for us. For example, when we run:

var user = Meteor.users.findOne({name: 'Michael'});
return user.name

to access the database and return result, Meteor automatically wraps the instructions in a fiber. The downside of this is that it become more complicated to use an asynchronous external APIs. By default, Meteor creates one fiber per DDP client.

What are the 3 commands used to interact with Meteor's internally used fibers?

  1. wrapAsync - attaches a callback to the current fiber
  2. unblock - enable multiple operations to execute in parallel within a single fiber
  3. bindEnvironment - create a new fiber to maintain the current environment, for example, global variable values.

What is the purpose of the wrapAsync function?

It takes an asynchronous function as argument and wait until that function finish and return the result of that function. When dealing with callbacks, you can use the Meteor.wrapAsync function to ensure that the result from the callback stay within a certain fiber. Without passing a callback as an argument, it calls the function synchronously. Otherwise, it actually is asynchronous. Only when a callback is provided will Meteor be able to restore the environment captured when the original function was called, effectively putting the result into the same fiber.

What is the purpose of the unblock function?

It enable multiple operations to execute in parallel perhaps by creating a new fiber for the same client.

What is the purpose of the bindEnvironment function?

Unlike the wrapAsync function, bindEnvironment creates a new fiber, and preserve the context of the current function.

How can we use wrapAsync?

setTimeoutFor3sCb = function (value, cb) {
  var result = value;
  Meteor.setTimeout(function() {
    console.log('Result after timeout', result);
    cb(null, result + 3);
  }, 3000);
}

Meteor.methods({
  'wrapAsyncMethod': function () {
    console.log('Method.wrapAsyncMethod');
    var returnValue = 0;

    // Use Meteor.wrapAsync:
    returnValue = Meteor.wrapAsync(setTimeoutFor3sCb)(returnVaue);
    console.log('resultComputatioin', returnValue);
    return returnValue;
  }
});

wrapAsync takes a standard callback function as the last parameter, with an error and response parameter: callbackFunction(err, result) {}.

How can we use unblock?

Inside a DDP Method:

this.unblock();

Using unblock allows Meteor to create a new fiber if the same client makes additional method calls.

How can we use bindEnvironment?

For certain operations, it's important to access the environment from which you made an asynchronous functional call. Lets assume that you have the accounts package added and you want to access the current userId. As long as you do so asynchronously, you can do it like:

Meteor.userId();

However, if you call a asynchronous function, the original environment is lost inside the callback. Suddenly, this has lost access to userId because this relates to the global object instead of the invocation (DDP) object.

When using asynchronous functions in a method that needs to be able to access the current environment, you can use Meteor's bindEnvironment function, which creates a fiber and attaches the correct environment automatically:

Meteor.methods({
  'bindEnvironment': function () {
    console.log('Method.bindEnvironment: ', Meteor.userId());
    setTimeoutFor3sCb(2, Meteor.bindEnvironment(function() {
      console.log('Method.bindEnvironment (3s delay):', Meteor.userId());
    }));
  }
});

How can we call external API asynchronously?

As you know, calling an external API should always be done asynchronously. Lets create a dedicated function for calling the API asynchronously:

var apiCall = function(apiUrl, callback) {
  try {
    var response = HTTP.get(apiUrl).data;
    callback(null, response);
  } catch (error) {
    if (error.response) {
      var errorCode = error.response.data.code;
      var errorMessage = error.response.data.message;
    } else {
      var errorCode = 500;
      var errorMessage = 'Cannot access the API';
    }
    var myError = new Meteor.Error(errorCode, errorMessage);
    callback(myError, null);
  }
};
Meteor.methods({
  getJsonForIp: function(ip) {
    this.unblock();
    var apiUrl = 'some URL' + ip;
    var response = Meteor.wrapAsync(apiCall)(apiUrl);
    return response;
  }
});
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License