Asynchronous Execution in JavaScript

Tarek Sherif

JavaScript is single-threaded

  • JavaScript, UI and rendering share the same thread
  • Blocking operations will hold everything up

Solution

  • Make as many operations asynchronous as possible.
    • AJAX
    • UI callbacks
    • File I/O
    • Web Workers
  • Basically:
    • Let the environment handle it
    • Provide a callback function to be executed at the end

Examples

          
  $.get("http://my-url", function(res) { 
    console.log(res); 
  });

  $("#clicker").click(function() { 
    console.log("I was clicked!"); 
  });

  setTimeout(function() {
    console.log("It's time!");
  }, 1000);
          
        

Non-Sequential

  • This requires a change in reasoning about your program
  • Statements don't execute sequentially

Example 1

What will be the output?

          
  var x;
  setTimeout(function() {
    x = "Hello";
  }, 1000);
  console.log(x);
          
        

Example 2

How about this?

          
  var x;
  setTimeout(function() {
    x = "Hello";
  }, 0);
  console.log(x);
          
        

JavaScript Execution Queue

  • When a callback is triggered it doesn't run immediately
  • It's placed in the execution queue
  • The environment will run queued callbacks in order

JavaScript Execution Queue

onload()

JavaScript Execution Queue

onload()onclick()

JavaScript Execution Queue

onclick()timeout()

JavaScript Execution Queue

onclick()timeout()onclick()

JavaScript Execution Queue

timeout()onclick()

JavaScript Execution Queue

onclick()

Async Programming Hints

Helper async function:

          
  // Take string and callback function.
  // Wait 0-500ms.
  // Call callback with string as argument.
  function async(string, callback) { 
    setTimeout(function() {
      callback(string);
    }, Math.random() * 500);
  }
          
        

Async in Order

Will this work?

          
  function printString(string) { console.log(string); }

  async("Print this first!", printString);
  async("Print this second!", printString);
          
        

Async in Order

Sometimes!

  • How can we execute them in order?

Async in Order

          
  function printString(string) { console.log(string); }

  async("Print this first!", function(string) {
    printString(string);
    async("Print this second!", printString);
  });
          
        

Parallelization

  • I want to do several independent asynchronous calls
  • Then combine their results
    • E.g. Return the longest string

Parallelization

          
  async("Why aren't we", function(string1) {
    async("running parallel?", function(string2) {
      var result = string1.length > string2.length ? string1 : string2; 
      console.log(result);
    });
  }); 
          
        

Parallelization

How do we write the callback?

          
  function combineResults(string) {
    // What goes here?
  }
  
  async("We are ", combineResults);
  async("running parallel!", combineResults);
          
        

Parallelization

How do we write the callback?

          
  var num_left = 2;
  var res = [];
  function combineResults(string) {
    res.push(string);
    if (--num_left === 0) {
      console.log(res[0].length > res[1].length ? res[0] : res[1]);
    }
  }
  
  async("We are ", combineResults);
  async("running parallel!", combineResults);
          
        

Parallelization

  • What if we want to keep things in order?
  • Will this work?
          
  var num_left = 2;
  var strings = ["Keep us", "in order"];
  var res = [];
  var i;

  for (i = 0; i < num_left; i++) {
    async(strings[i], function(string) {
      res[i] = string;
      if (--num_left === 0) {
        console.log(res.join(" "));
      }
    });
  }
          
        

Parallelization

Create a closure!

          
  var num_left = 2;
  var strings = ["Keep us", "in order"];
  var res = [];
  var i;

  for (i = 0; i < num_left; i++) {
    (function(i){
      async(strings[i], function(string) {
        res[i] = string;
        if (--num_left === 0) {
          console.log(res.join(" "));
        }
      });
    })(i);
  }
          
        

Web Workers

True multi-threading

          
  var worker = new Worker("url/of/worker.js");

  worker.addEventListener("message", function(event) {
    console.log(event.data);
  });

  worker.postMessage("Hello");

  // ---- IN THE WORKER SCRIPT ----
  self.addEventListener("message", function(event) {
    self.postMessage("Got message: " + event.data)
  });
          
        

Callback Hell

When async calls are interdependent

          
  async("Step 1", function(result) {
    console.log(result + " done!");
    async("Step 2", function(result) {
      console.log(result + " done!");
      async("Step 3", function(result) {
        console.log(result + " done!");
      });
    });
  });
          
        

Callback Hell

Mitigate by pulling out callbacks

          
  async("Step 1", doneStep1);
  
  function doneStep1(result) {
    console.log(result + " done!");
    async("Step 2", doneStep2);
  } 
  
  function doneStep2(result) {
    console.log(result + " done!");
    async("Step 3", doneStep3);
  }
  
  function doneStep3(result) {
    console.log(result + " done!");
  }
          
        

EcmaScript 6

Next version of JavaScript

  • Two new constructs to help with async programming
    • Promises
    • Generators

Promises

Return an object that handles callbacks

          
  function get(url) {
    return new Promise(ajaxResolutionFunction);
  }
  
  get("http://first-url").then(function(result) {
    console.log(result);
    return get("http://next-url");
  }).then(function(result) {
    console.log(result);
    return get("http://last-url");
  }).then(function(result) {
    console.log(result);
  })
          
        

Generators

Craziness

          
  fancyAsync(function*(handleResult) {
    var res  = yield async("It's as if ", handleResult); 
    res     += yield async("I'm running ", handleResult);
    res     += yield async("synchronously!!!", handleResult);
    console.log(res);
  });

  // Where the magic happens.
  function fancyAsync(generator) {
    var g = generator(handleResult);
    g.next();
    
    function handleResult(response) {
      g.next(response); 
    }
  }
          
        

Thanks!

tsherif@gmail.com

http://tareksherif.ca

@thsherif