Writing async code in Meteor


Javascript is single-threaded, meaning that it can do only one thing at a time. This would imply a slow operation would “block”, and everything else would have to wait for the slow operation to finish before they can continue.

But in reality, this doesn’t happen thanks to asynchronous calls. Asynchronous calls let us perform long-running I/O operations (Input/Output) without blocking. I/O operations consist of operations such as network requests and disk reads/writes.

To understand “blocking”, consider the two functions below.

function printTwoAsync() {
  makeRequest(`posts?userId=2`, (err, results) => {
    console.log(results[0].userId);
  });
}

function printTwoSync() {
  console.log("2");
}

Note: If the => looks unfamiliar to you, these are ES6 standards for writing Javascript. I strongly recommend checking out Top 10 ES6 Features for a quick introduction and ES6 Features for the full documentation.

Note that the printTwoSync() just prints the number two, while printTwoAsync() grabs the number 2 from a network call, which means it is doing a non-blocking I/O operation that is slower than printTwoSync().

Now consider a case where we want to print the numbers 1,2,3 using the functions above to print the 2. Before reading the rest, try to guess the outcome of each of the functions below.

function blockIO(){
  console.log('1');
  printTwoSync();
  console.log('3');
}


function nonBlockIO(){
  console.log('1');
  printTwoAsync();
  console.log('3');
}

The output of the blockIO() function, is:

1
2
3

This is expected. Javascript waits for printTwoSync() to finish in order to run the next line.

However, the output of nonBlockIO() is:

1
3
2

This is because in the second case printTwoAsync() is a slow operation and Javascript doesn’t wait for it to return in order to run the next line. However, once printTwoAsync() has finished its task, its callback is fired and 2 is printed. If we want to keep the order, console.log(3)would have to be in printTwoAsync()‘s callback. We will get into what callbacks are below.

What are callbacks and why they are useful ?

Let’s say that your app is trying to find a user on a third party website, find their first post and the first comment from that post. Also, let’s assume that you need to do the following steps in order to achieve that:

  • Find the user’s id
  • Find all posts by that user and take the id of the first post
  • Find the first comment on the given post and print it

One can observe that these operations can be different than, say, an addition operation because they take some time to come back with a result. However, these operations, like other I/O operations in Javascript, take advantage of a callback which is a function that is called once the HTTP request comes back. The callback usually has a potential error or null as the first parameter and the desired value as the second variable

function getFirstCommentCallback() {
  makeRequest(`users/1`, (userErr, userResults) => {
    if (userErr) {
      throw new Meteor.Error(userErr);
    }
    const userId = userResults.id;
    makeRequest(`posts?userId=${userId}`, (postErr, postResults) => {
      if (postErr) {
        throw new Meteor.Error(postErr);
      }
      const postId = postResults[0].id;
      makeRequest(`posts/${postId}/comments`, (commentsErr, commentsResults) => {
        if (commentsErr) {
          throw new Meteor.Error(commentsErr);
        }
        console.log(commentsResults[0]);
      });
    });
  });
}

If you don’t already see the problem, it’s the pyramid of sad mustachioed winking faces, aka the infamous callback hell. The code can get very messy very quickly.

ES6 – Promises

An alternative solution to the pyramid of frowny faces is using promises, which were introduced in ECMAScript 6. One of the main differences between callbacks and promises is that callbacks are built with a pattern that the Javascript community has agreed to follow (e.g. Node.js convention), but it is not enforced in the language. Whereas Promises have a defined structure built in the language.

On the other hand, Promises are structured to only have the following states:

  • fulfilled/rejected : Indicating if the chain of promises should continue
  • pending/settled : Indicating whether or not the promise has been given the fulfilled or rejected status

To understand how promises work, observe the following example which is very similar to the caolan/async example:

function getFirstCommentPromises(){
  makeRequestPromise(`users/1`)
  .then(results => makeRequestPromise(`posts?userId=${results.id}`))
  .then(results => makeRequestPromise(`posts/${results[0].id}/comments`))
  .then(results => console.log(results[0]))
  .catch(err => {
    throw new Meteor.Error(err);
  });
}

ES7 Async/Await:

With ES7, async/await was introduced, which along with promises make life much easier. The good news is that this already works in Meteor 1.3 so you don’t need any extra libraries for it to run. This method allows the developer to write synchronous-looking code without worrying about callbacks.

The example above can be re-written using async/await like below:

async function getFirstCommentAsyncAwait() {
  try {
    userResults = await makeRequestPromise(`users/1`);
    postResults = await makeRequestPromise(`posts?userId=${userResults.id}`);
    comments = await makeRequestPromise(`posts/${postResults[0].id}/comments`);
    console.log(comments[0]);
  } catch (err) {
    throw new Meteor.Error(err);
  }
};

What about Meteor and Fibers?

Meteor is built on Node.js, so the issue with being single threaded and callback hells would be inherited if it wasn’t for Fibers. Fibers provides an abstraction layer for Node’s eventloop so that we don’t have to deal with callbacks and other related issues.

Let’s see how our example would look like if we took advantage of this Meteor feature. You can wrap you asynchronous function using Meteor.wrapAsync, which makes it possible to use it like a synchronous function like below:

const makeRequestSync = Meteor.wrapAsync(makeRequest);

function getFirstCommentWrapAsync() {
  try {
    userResults = makeRequestSync(`users/1`);
    postResults = makeRequestSync(`posts?userId=${userResults.id}`);
    comments = makeRequestSync(`posts/${postResults[0].id}/comments`);
    console.log(comments[0]);
  } catch (err) {
    throw new Meteor.Error(err);
  }
}

It can be observed that the syntax is almost identical to the async/await example. However, it seems that “in the future, once async/await is more supported, Meteor will simply get rid of fibres. They are migrating to using the async/await abstraction, rather than using Fibers directly”. Therefore, I personally suggest using async/await, because it will be ES7 standard very soon and your piece of code will be compatible in any other Javascript environment.

The good thing about both async/await and fibers is that they allow you to write code in a synchronous style, i.e. directly using the returned value, without having to care that the single-threaded loop interrupted the function partway through and then resumed. This is much more readable because it’s closer to what you would write in any kind of pseudo-code.

I will be using meteor.wrapAsync in the server code and will be writing about in the next posts where I am using Meteor Restivus.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s