Synchronous asynchronous JavaScript

In one of my current nodeJS projects I’ve needed specific asynchronous functions to return sequentially. It’s not really uncommon at all - and it’s not only limited to nodeJS.

With more and more frameworks/tools providing the ability to perform non-blocking functionality - this ‘problem’ arises when you need to ensure those requests have completed before your code can move on.

So here are a couple of different variations I looked at using to solve this problem.

Using plain ‘ol JavaScript, I’ll run through an example of each and then announce my winner at the end.

TL;DR

Promises

The Problem

Asynchronous tasks are awesome. They don’t block our code, so our script can make an async request - move on with other tasks - knowing that at some point we’ll get a reponse down the line when we need it.

But what if we need these responses before moving on?

So for an example we’ll look at making a twitter timeline - where we need to make 3 separate XHR async requests to get a few specific tweets. But seeing as they are asynchronous there’s really no guarantee of the order we’ll them get back - or when.

Here are the example tweets we’ll be requesting - in order.

Tweet 1

{
  "user": {
    "name": "Lonely Bob",
    "handle": "lonelyBob"
  },
  "message": "Oh man, did you just hear that? What was that?!!!",
  "favourited": true
}

Tweet 2

{
  "user": {
    "name": "Lonely Bob",
    "handle": "lonelyBob"
  },
  "message": "I'm wearing jean shorts!",
  "photo": true
}

Tweet 3

{
  "user": {
    "name": "Lonely Bob",
    "handle": "lonelyBob"
  },
  "message": "What would you do if there wasn't gravity?",
  "photo": true,
  "favourited": true
}

Once we have our tweets - we can then tally up the amount of tweets we have and some of the properties (how many were favourited / how many have photos, etc).

We want to get all those tweets - in order - before we show a timeline and tally up the stats.

Let’s do it - to it

We’ll make 2 of those request non-blocking asynchronous XHR calls, while the other we’ll include some blocking code (meaning nothing else can happen until it is completed - simulating a delayed response from the server) by wrapping it in a setTimeout() function.

Example 1 - Callback hell

We’ve all heard of it - and we’re all trying not to end up there.

// our first async req
get('https://api.myjson.com/bins/2qjdn',
  function(data){
    totalTweets.push(data.response);

    // our second async req wrapped in a 2 second delay
    setTimeout(function(){
      get('https://api.myjson.com/bins/3zjqz',
        function(data){
          totalTweets.push(data.response);

          // our third async req
          get('https://api.myjson.com/bins/29e3f',
            function(data){
              totalTweets.push(data.response);
              showStats();
            },
            function(err){
              onError(err);
            }
          );

        },
        function(err){
          onError(err);
        }
      );
    }, 2000);

  },
  function(err){
    onError(err);
  }
);

view the Gist

Gross, right ? … and thats only 3 callbacks.
This is what we definitely don’t want. Imagine having to maintain this code or something deeper than this. This is making me sick just looking at it. Let’s move on.

Callbacks ( slightly nicer )

So keeping with the callback theme - we could break this up into a more legible way by splitting it into a series of functions.

// our first async req
function get1stTweet(){
  get('https://api.myjson.com/bins/2qjdn',
    function(data){
      totalTweets.push(data.response);

      // call get2ndTweet()
      get2ndTweet();
    },
    function(err){
      onError(err);
    }
  );
}


// our second async req wrapped in a 2 second delay
function get2ndTweet(){
  setTimeout(function(){
    get('https://api.myjson.com/bins/3zjqz',
      function(data){
        totalTweets.push(data.response);

        // call get3rdTweet()
        get3rdTweet();
      },
      function(err){
        onError(err);
      }
    );
  }, 2000);
}

// our third async req
function get3rdTweet(){
  get('https://api.myjson.com/bins/29e3f',
    function(data){
      totalTweets.push(data.response);

      // call showStats()
      showStats();
    },
    function(err){
      onError(err);
    }
  );
}

// kick it off
get1stTweet();

View the Gist

It’s a little more legible, but each function has a dependence on another function by explicitly referencing it. I know we can clean this up a little more.

Callbacks ( slightly nicer still )

// our first async req
function get1stTweet(){
  get('https://api.myjson.com/bins/2qjdn',
    function(data){
      totalTweets.push(data.response);

      // call next()
      next();
    },
    function(err){
      onError(err);
    }
  );
}

// our second async req wrapped in a 2 second delay
function get2ndTweet(){
  setTimeout(function(){
    get('https://api.myjson.com/bins/3zjqz',
      function(data){
        totalTweets.push(data.response);

        // call next()
        next();
      },
      function(err){
        onError(err);
      }
    );
  }, 2000);
}

// our third async req
function get3rdTweet(){
  get('https://api.myjson.com/bins/29e3f',
    function(data){
      totalTweets.push(data.response);

      // call next()
      next();
    },
    function(err){
      onError(err);
    }
  );
}

// Define a queue of functions
var fns = [get1stTweet, get2ndTweet, get3rdTweet, showStats, showTimeline];

// Sequentially work our way through the array of functions
function next(){
  var fn = fns.shift();

  if (fn != null) {
    fn();
  }
  else {
    // just in case a method calls next() and there
    // are more functions in `fns` to call
    return;
  }
}

// lets kick it off!
next();

View the Gist

It’s a tiny bit more code, but now all 3 requests are relying on the one function instead referencing each other. So we could happily remove reference to get2ndTweet and our code will keep on keeping on.

But I still feel like this could achieve in a nicer way.

Promises

A Promise represents a task which may be resolve or rejected - and can also then be chained with .then() methods. Promises are deferred objects - something that started at one time and resolved or rejected later down the track.

Once a promise has been resolved/rejected it’s passed to a .then() method, which takes 2 optional params - a success function and an error function.

get('some-url')
.then(

  function(data){
    // do something with data
  },
  function(err){
    // do something with error
  }

);

But the awesomeness of a Promise comes into play when you start to chaining .then() methods.

get('some-url')
.then(
  function(data){

    // do something with `data`
    return get('some-other-url');

  })
.then(
  function(data){

    // do something with `data`
    return get('some-strange-url');

  })
.then(
  function(data){

    // do something with `data`
  },
  function (err) {

    // do something with the error
  }
);

View the Gist

Notice how theres only one error callback?!

As a Promise is resolved or rejected, it travels down the chain. So if any of the .then() methods are rejected, it will travel down to our last defined error handler, freeing up a lot of code by reducing the number of error callbacks.

It might not be a legible as splitting it out into separate functions, but it requires less code, and is far more legible than callback hell.


(do this)
.then((do this))
.then(
  (do this),
  (or throw error)
);

The results are in!

You can easily see that Promises are the clear winner here. Sure they do have a level of complexity that might seem daunting from the ‘get go’ - but it’s nothing a developer can’t master.

Depending on what browser you’re targeting you might not be able to use Promises out of the box, in which case you’ll need to include the ES6-Promises polyfill.

But how do you check if Promises are supported in your browser? Just wang the following into your console.

if (Promise) {
  alert("Promise is supported!");
}

For more resources on promises - checkout JavaScript Promises - by html5rocks, Promises - by Forbes Lindesay and JavaScript Promises … In Wicked Detail - by Matt Greer