Understanding Async

Many people (especially newcomers to Node.js) have a lot of trouble understanding asynchronous programming. Since I like analogies, I've come up with the following one to help folks understand the difference between synchronous and asynchronous situations.

Your goal is to buy an outfit: a shirt, a pair of pants*, and a pair of shoes.

Synchronous Programming

You go to the shopping mall. Your first stop is to The Shirt Store. Having found the shirt you like, you go to the cashier, pay for your shirt, and walk out with your shirt. Next, you go to The Pants Store. Again, you pick out a pair of jeans, head to the cashier, pay, and walk out. Finally, you enter The Shoe Store. You grab your pair of shoes, pay for them at the cashier, and walk out. Now that you've got your shirt, pants, and shoes, you can go home and party on.

Asynchronous Programming

You are at home and are shopping online. You go to TheShirtStore.com, pick your shirt, and pay for it. Then you head to ThePantsStore.com, select your pants, and pay. Finally, you direct your browser to TheShoeStore.com and buy a pair of shoes. Over the course of the next week, your purchases show up via delivery. Your shoes come in first, then your shirt, and then your pants. Now that you've got your shirt, pants, and shoes, you can party on! (You don't need to go home, because you're probably home already.)

The Inherent Difference

With synchronous programming, each operation is what we call "blocking." This means that each operation must finish in its entirety before the next operation can start. In the shopping analogy, you can't buy another piece of clothing until you have acquired the piece of clothing that you have just purchased.

With asynchronous programming, there's no blocking. The operations start in sequence, but then might finish at different times. In the shopping analogy, you can buy all of your clothes without having any of them in your possession yet. You're not blocked from making your next purchase, though the (potential) downside is that you can't necessarily guarantee the order in which your purchases will arrive at your doorstep.

Let's look at it with code

// Synchronous
var shirt, pants, shoes;

var getItemSync = function (storeName, itemName) {  
  var store = goToStore(storeName);
  var item = store.getItem(itemName);
  console.log('I got my ' + itemName);

  return item;
};

shirt = getItemSync('The Shirt Store', 'shirt');  
pants = getItemSync('The Pants Store', 'pants');  
shoes = getItemSync('The Shoe Store', 'shoes');

Your output:

I got my shirt!  
I got my pants!  
I got my shoes!  

As compared to...

// Asynchronous
var shirt, pants, shoes;

var getItem = function (storeName, itemName) {  
  var store = goToStore(storeName);
  var item = store.getItem(itemName);
  console.log('I got my ' + itemName);

  return item;
};

shirt = getItem('The Shirt Store', 'shirt');  
pants = getItem('The Pants Store', 'pants');  
shoes = getItem('The Shoe Store', 'shoes');

Your output:

I got my shoes!  
I got my shirt!  
I got my pants!  

Synchronizing the asynchronous

Now let's talk about callbacks. A callback basically says, "Hey, I'm done!" (ahem: it calls... back.)

When working in an asynchronous environment, you use callbacks to tell a process to only execute the next process once the one that's currently being completed has finished. It looks like this:

// Using callbacks

var getItem = function (storeName, itemName, cb) {  
  var store = goToStore(storeName);
  var item = store.getItem(itemName);
  console.log('I got my ' + itemName);

  return cb(null, item); // we're assuming no errors here - this is bad, but it's just an example, so... meh.
}

The key piece of information here is that in JavaScript, we can pass functions in as parameters. In Node.js, callbacks are generally in the form of cb(error, result) - it's just a convention, but it's a useful one.

This then means that we can write our aysnchronous code like this:

// Sync-ified-ish Async
var shirt, pants, shoes;

getItem('The Shirt Store', 'shirt', function (err, theShirt) {  
  if (err) return err; // do proper error handling please
  shirt = theShirt;
  getItem('The Pants Store', 'pants', function (err, thePants) {
    if (err) return err; // this is just an example
    pants = thePants;
    getItem('The Shoe Store', 'shoes', function (err, theShoes) {
      if (err) return err; // not necessarily the best way to treat errors
      shoes = theShoes;
    });
  });
});

Your output:

I got my shirt!  
I got my pants!  
I got my shoes!  

And... that's it. Tada!

;-)


* or trousers, for our pants-mean-underwear readers ;-)