Fork me on GitHub

Ahad Bokhari

Asynchronous JavaScript

Edit   ·   Mix { ... } / 1200 words

I’ve been ruminating over asynchronous JavaScript and pondering over how it’s evolved and how we arrived at the state of our ecosytem as it stands today. The way we write asynchronous code has evolved drastically over the past years, and we need to wrap our heads around newer syntax and specifications, where we’ve come from and where we’re going. I’d like to do that syntactically with some useful examples that you can follow in our short journey in this post.

Before starting I’d like to explicitly state some points to remember, especially for those who are used to writing in a more synchronous way. After all, not everyone is lucky enough to write Node code day in and out.

  • Wrapping your head around asynchronous JavaScript can be challenging because our minds think an sequential manner - the term multitasking is really our minds switching between tasks so quickly that we don’t register it as a sequential step of tasks. Therefore it takes practice so be patient and work through clickable examples when learning.
  • Asynchronous development in the browser happens:
    • Browser API calls like setInterval(), setTimeout(), setImmediate()
    • DOM Events
    • XHR (XML HTTP Requests), or Ajax calls
  • If you’re aspiring to be a universal JavaScript developer an understanding of asynchronous processes that happen in modern day web applications is a must with Node on the rise.
  • If JavaScript functions were not first-class citizens of the language and could be passed around like any other variable or value we would not be here today.

Asynchronous vs Synchronous & Callbacks

In the browser mostly a direct style of programming is synchronous where each taks follows another task until the tasks are complete. Execution is returned from the first task before the next can begin.

It’s quite straightforward to follow the steps and the chain of execution:

function start() {
  console.log('Im starting');
}

function cont() {
  console.log('continuing...');
}

function end() {
  var arr = [1, 2, 3, 4, 5];
  console.log('The end: ' + arr);
}

start();
cont();
end();
// ==> 'I'm starting' 
// ==> 'continuing...'
// ==> 'The end: [1, 2, 3, 4, 5]

We can take the above sequential code and introduce a delayed execution of commands by using setTimeout or even setInterval, a native JS function.

function start() {
  console.log('Im starting');
}

function cont() {
  console.log('continuing...');
}

function end() {
  var timer = setTimeout(function() {
  var arr = [1, 2, 3, 4, 5];
  console.log('The end: ' + arr);
  }), 3000);
}

start();
end();
cont();
// ==> 'I'm starting' 
// ==> 'continuing...'
// ..console waits three seconds
// ==> 'The end: [1, 2, 3, 4, 5]

Welcome to your first callback using setTimeout! Notice the order of invocation first and take away that even though end() is invoked before cont() it doesn’t block the sequence of events - I’ll leave that upto you to figure out why. I can hint the above code worked because we used setTimeout, an async callback, to enact a delayed execution.

We can even write our own callbacks:

// Call our main function. Pass it a URI and a callback function
getData('https://fakeapi/some-end-point', renderData );

function getData(uri, callback) {

    // Simulate a 3 second delay, re-enacting the server response
    var timer = setTimeout(function () {

        // here's the array of data
        var dataArr = [1000, 10000, 100000, 1000000];

          //our callback function
        callback(dataArr);

    }, 3000);
}

function renderData(myData) {
    console.log(myData);
}

So, this is how we’ve been writing asychronous JavaScript for 15 years, you should note some of the drawbacks of using them:

  • If not used properly you can turn your code into spaghetti, the term is called callback hell.
  • you can’t use a return statement, nor throw errors
  • error handling can become tough

Promises

The JavaScript community figured out these limitations which weren’t easy to read or write and therefore introduced Promises. Promises date back a while and have become very popular in modern day applications, you should wrap your head around them. Currently the native specification of Promise API exists in ES6 but there are many other libraries that make them more efficient and offer more functionality. That said, you can pop open your console and write a promise right now.

Promises give us a handy way of writing asynchronous code in a clearer way to read and write. A promises represents a value that we will handle in the future - in a nutshell you should remember that’s basically it - a promises gives us guarantees about the future of the value.

Creating a simple immediately resolved promise:

var p = Proimise.resolve('norf');
p.then((result) ==> console.log(result);
//==> norf

A very contrived example indeed, below we look at a more involved piece of code for creating a standard promise:

var p = new Promise(function(resolve, reject) {  
   if (/* condition */) {
      resolve(/* value */);  // fulfilled successfully
   }
   else {
      reject(/* reason */);  // error, rejected
   }
});

Typically a promise is created by using the new Promise contructor which accepts a handler that is given two functions as it’s paramaters, resolve and reject. Therefore a promise has the three states:

  • Pending
  • Fullfilled
  • Rejected

I’ll leave it to you to come to do your research :-)

..then a consumer of your promise will come along and do something like this:

p.then(function() {
    console.log('success');
  })
  .catch(function() {
    //error handling
  })

Notice a Promises’s .then() method which also takes two paramaters. The first is the function to be clled with then Promise is fullfilled whilst the secons is a function to be called if the promise is rejected. When dealing with errors first remember that throwing an exception in a Promise will automatically reject that Promise.

  • Use the idiomatic .catch() for handling errors (see the chained .catch() in the above example)
  • Be aware of Promise.race() and Promise.all()

There is alot more you can learn about Promises, we’ve just touched the tip if the iceberg. I recommend looking at the Promises/A+ specification.

Generators / Yield

We’ve covered callbacks and promises, which both have been around for some time. JavaScript Genertors are a relatively new concept and were intorduced in ES6. Generator functions when you execute them cna be paused at any point while you calculate something else, then return and continue from your paused state.

For example:

function* baz () {  
   let index = 0;
   while (index < 2) {
     yield index++;
   }
 }
 var quux =  baz();

 console.log(quux.next());    // { value: 0, done: false }  
 console.log(quux.next());    // { value: 1, done: false }  
 console.log(quux.next());    // { value: undefined, done: true }  
 

We need to observe that generators differ from normal functions with respect to the run to completion expectation. A fundamental aspect of your functions is that when it starts running, it will always run to completion before any other JS code can run. With generators we have a different type of function, one that can be paused in the middle and resumed later, allowing other code to run during these paused periods.

The challenges of multi threaded programming - in JavaScript land however we do not have to worry about mulit threads because JS is always single threaded. Simply this means only one function or command will execute at any given time.

Async / Await

Async functions are introduced in ES7, and are only available through a transpiler. With async functions or the async keyword we are essentially doing what we are doing with generators, without the extra fluff.

A short example:

async function loadArticle() {
   try {
     let story = await getJSON('article.json');
     addHtmlToPage(article.heading);
     for (let chapter of story.articleURLs.map(getJSON)) {
       addHtmlToPage((await snippet).html);
     }
     addArticleToPage("Complete");
   } catch (err) {
     addArticleToPage("Something broke: " + err.message);
   }
   document.querySelector('.spinner').style.display = 'none';
 }
 

Congratulations, you’ve gotten to the end of the article - pat yourself on the back. I strongly suggest that before looking at more advanced options(as above: promises, generators) you should learn how to read and write callbacks proficiently. Pretty much everything else depends upon the understanding of fundamental callbacks.