JavaScript asynchronous and parallel execution

JavaScript is a historically single threaded programming language, which means it was never designed to run multiple tasks simultaneously. However, throughout the years, JavaScript has added features to make it easier to perform multiple tasks with its single threaded model.

Single threaded, the problem; asynchronous and parallel execution, the solutions

Being single threaded means there's only one available path to execute tasks, so if you want to execute tasks A, B & C, only one task can run at any given time. This behavior, is JavaScript's default behavior and is shown is listing 7-1.

Listing 7-1. JavaScript default behavior to run one task at any given time
function primeNumbersTo(max)
{
    var store  = [], i, j, primes = [];
    for (let i = 2; i <= max; ++i) 
    {
        if (!store [i]) 
          {
            primes.push(i);
            for (let j = i << 1; j <= max; j += i) 
            {
                store[j] = true;
            }
        }
    }
    console.log("Primes from max %s: %s", max, primes);
    return primes;
}

// Start timer
console.time();
console.log("About to get primes");
console.log("Getting primes up to 10");
primeNumbersTo(10);
console.log("Getting primes up to 10_000");
primeNumbersTo(10_000);
console.log("Getting primes up to 10_000_000");
primeNumbersTo(10_000_000);
console.log("Finished getting primes");

console.log("I'm a really urgent log message, I had to wait...");
// End timer 
console.timeEnd();

Listing 7-1 starts with the primeNumbersTo() function that calculates all the prime numbers up to a maximum number input. Since the primeNumbersTo() function can take a noticeable amount of computation time, the next step consists of starting a timer, followed by three calls to the primeNumbersTo() function using different inputs and stopping the timer to record the total time. More importantly, notice that prior to stopping the timer, there's a log message that says "I'm a really urgent log message, I had to wait...", which of course has to wait for the three function calls before its output. Depending on your machine, the wait time for this last log message can vary between 1.5 to 3 seconds. Such a log message could have equally been a more important task, like showing a success or error message for an end user, that took 1.5 to 3 seconds to appear.

One of the positives of this single-threaded behavior illustrated in listing 7-1 is it greatly simplifies program design. Since you know beforehand no two tasks can run simultaneously, there's no danger of one task overwriting the work of another task, subsequent tasks missing data from prior tasks that haven't yet finished or the task completion order being altered due to different execution times. So if task A starts, task B can't start until task A finishes, and task C won't start until task B finishes, and so on.

One of the negatives of this single-threaded behavior is it can make tasks run into unnecessary delays. For things like a UI (User Interface) or an I/O operation like reading/writing a file or a network response, you don't want users experiencing a frozen UI while another task takes multiple seconds to finish, like you also don't want to stop everything for multiple seconds while a task reads/writes a large file or a response is received from a remote service.

There are two solutions to improve this single-threaded behavior, which allow tasks to be executed without having to wait for long lived tasks: asynchronous execution & parallel execution.

With asynchronous execution, you'll still have a single thread capable of running only one task at any given time, however, the execution process decouples requests from responses, allowing the single thread to move from request to request without having to wait for responses. In listing 7-1, you can see that after each request is made to the primeNumbersTo() function, the single thread has to wait until a response is received from the function. With asynchronous execution, requests don't depend on responses being received to move forward to other requests. To implement asynchronous execution, JavaScript relies on a many concepts that include: timeouts, callbacks, promises & async functions.

With parallel execution, you'll also have the same single thread capable of running only one task at any given time, however, you'll be able to spawn a separate thread to place tasks that have a propensity to take a long time. So in listing 7-1, instead of making requests directly on JavaScript's single(main) thread, you can place such requests to calculate prime numbers in a thread that runs in parallel to JavaScript's single(main) thread. In this manner, JavaScript's single(main) thread can continue its work without having to wait for other tasks. To implement parallel execution, JavaScript relies on the concept of Web workers.

Timeouts, delaying the inevitable and not caring about execution order 

One of the earliest techniques to alleviate JavaScript's single-threaded behavior shown in listing 7-1 is to use timeouts. In such cases, you alleviate JavaScript's single(main) thread being overtaken by any one task by simply postponing tasks to run at a later time. Strictly speaking timeouts produce asynchronous behavior, since tasks are decoupled from JavaScript's single(main) thread and allow other tasks to run right away, however, there's a subtle behavior related to another characteristic that's often used interchangeably with asynchronous behavior: non-blocking behavior.

Timeouts prevent tasks from blocking JavaScript's single(main) thread temporarily, however, once a timeout is reached, tasks use the one and only JavaScript single(main) thread to complete their work. This means there's always a possibility a task's underlying logic ends up blocking the single(main) thread, it's avoided at the point where a timeout is declared, but eventually a task's logic has to run on the single(main) thread to perform its work where it can have blocking behavior.

Listing 7-2 illustrates various examples of JavaScript timeouts using the setTimeout() and setInterval() methods.

Listing 7-2. JavaScript timeouts for asynchronous behavior
function longTask(message) {
  console.log("Message %s took...", message);
  console.timeLog();
}

// Start timer
console.time();
console.log("About to print messages");

let firstTimeoutId = setTimeout(longTask, 5000, "First task");
let secondTimeoutId = setTimeout(longTask, 0, "Second task");
let thirdTimeoutId = setTimeout(longTask, 2000, "Third task");
let repeatIntervalId = setInterval(longTask, 3000, "Repeat task");
console.log("firstTimeoutId: %s", firstTimeoutId);
console.log("secondTimeoutId: %s", secondTimeoutId);
console.log("thirdTimeoutId: %s", thirdTimeoutId);
console.log("repeatIntervalId: %s", repeatIntervalId);


console.log("Finished printing messages");

The most important behavior in listing 7-2 is the last log message console.log("Finished printing messages"); is output right after the first log message console.log("About to print messages");, which confirms the calls made to the longTask() function don't block JavaScript's single(main) thread, immediately at least.

The calls made to the longTask() function are wrapped inside either the setTimeout() or setInterval() methods, which are part of the Window interface, and as such, they can be called directly or fully qualified with the window keyword (e.g. window.setTimeout()) in most JavaScript environments.

The purpose of the setTimeout() or setInterval() functions is to defer the execution of a function or code passed as the first argument, by an amount of milliseconds passed as the second argument, with the remaining arguments used as arguments for the function or code specified in the first argument. The difference between both methods is, setTimeout() runs its logic once after its specified time, while the setInterval() runs its logic in an endless loop every time its specified time is reached.

You can see in listing 7-2 the different times used for each longTask() call range from 0 to 5000 milliseconds, with the setInterval() function call occurring in multiples of its time span and the setInterval() function call occurring only once after its time span is reached.

Next, you can see the results of each call are assigned to variables, with the result of each call producing a Timeout object. This object contains metadata about each call, with the possibility to use it to cancel the call before its execution time is reached. Canceling calls is helpful, particularly for calls made with the setInterval() function since they never stop or for calls made with the setTimeout() function in case a condition is met that warrants canceling it.

To cancel a call made with the setTimeout() or setInterval() methods before it reaches its execution time, you can use the clearTimeout(Timeout_object) or clearInterval(Timeout_object) methods, respectively, which requires a Timeout object input of the call to cancel. Both the setTimeout() or setInterval() methods are part of the Window interface, so they can also be called directly or prefixed the window keyword reference (e.g. window.clearTimeout(Timeout_object)).

A no less important behavior of timeouts shown in listing 7-2 is completion order. While the setTimeout() and setInterval() statements in listing 7-2 ensure the first statement (i.e. console.log("About to print messages"); ) and last statement (i.e.console.log("Finished printing messages"); are completed first and second & one after the other, no similar order assurances can be made for the other statements. Although the time assigned to each request (e.g. 0, 2000, 3000, 5000) is a strong indicator of task execution order, there's no guarantee the completion of each task will be in this order, particularly for tasks that perform complex logic that can vary widely in their completion times. This makes timeouts unsuitable for scenarios where completion order is important or the logic for certain tasks depend on the result of other tasks.

Callbacks and higher-order functions: They aren't necessarily asynchronous & non-blocking

Callbacks are a general purpose technique used across programming languages. Callbacks represent a piece of code that gets passed as an argument to another piece of code, with the expectation the former is executed by the latter when a certain thing happens. Since you're more likely to encounter callbacks in the context of functions, let's rephrase the definition in terms of functions: Callback functions are what gets passed as an argument to another function, with the expectation the callback function gets executed by the receiving function when a certain thing happens.

To avoid ambiguity when referring to functions used in so many ways, it's worth pointing out that functions that accept arguments that are functions are called higher-order functions. So after dissecting the different concepts, we can say:

Callback functions are what gets passed as arguments to higher-order functions, with the expectation the higher-order functions execute the callback functions when a certain thing happens.


JavaScript having first class functions means this ability to treat functions as values that can be passed around to other functions is a core part of the language. And this also means any function can operate as a callback function, as well as a higher-order function. For example, in listing 7-2, setTimeout() is the higher-order function, while longTask() is the callback function and the timeout value in milliseconds is the certain thing the higher-order function waits for to execute the callback.

So with this kind flexibility, there's a wide range of possibilities for functions to be either higher-order or callback, as well as many things to act as a trigger for a higher-order function to invoke a callback, such as: elapsed time, a click, completion of work, a tap, or some other thing.

Listing 7-3 illustrates a refactored version of listing 7-1 that makes use of a callback function.

Listing 7-3. JavaScript synchronous callback with Array.forEach()
function primeNumbersTo(max)
{
    var store  = [], i, j, primes = [];
    for (let i = 2; i <= max; ++i) 
    {
        if (!store [i]) 
          {
            primes.push(i);
            for (let j = i << 1; j <= max; j += i) 
            {
                store[j] = true;
            }
        }
    }
    console.log("Primes from max %s: %s", max, primes);
    return primes;
}

let primeNumbers = [10, 10_000, 10_000_000];

// Start timer
console.time();
console.log("About to get primes");

primeNumbers.forEach(primeNumbersTo);

console.log("Finished getting primes");

console.log("I'm a really urgent log message, I had to wait...");
// End timer 
console.timeEnd();

Listing 7-3 defines the same primeNumbersTo() function from listing 7-1 and declares the array primeNumbers to call the function with three different values. However, the difference is this example uses the Array.forEach() method to loop over each prime number in primeNumbers and call the primeNumbersTo() function.

In this case, the Array.forEach() method is the higher-order function, while primeNumbersTo() is the callback function and each iteration over the loop is the certain thing the higher-order function waits for to execute the callback. The Array.forEach() syntax in this case is the simplest possible, called on an array and requiring the input to be a callback method to invoke with each element of the array.

The outcome of executing listing 7-3 is identical to the one in listing 7-1, with a wait time between of 1.5 to 3 seconds between the first and last log message "I'm a really urgent log message, I had to wait...".

So why are the results the same ? Shouldn't things run faster due to the callback ? Not really, things run the same because the callback function -- in this case primeNumbersTo() -- still blocks JavaScript's single(main) thread from the time a request starts to the time a response is received. So the only difference between listing 7-1 and listing 7-3 is the latter uses a loop to trigger the primeNumbersTo() callback function on each iteration over an array, while the former simply calls the primeNumbersTo() function multiple time with different values.

This is the same blocking behavior shown in listing 7-2 and described toward the end of the last section. While setTimeout() is the higher-order function and longTask() is the callback function, the longTask() callback function can still eventually block JavaScript's single(main) thread once the trigger for the higher-order function -- a timeout -- is reached. What's a little deceptive about examples with timeouts is the trigger is a time delay, which makes it appear the JavaScript single(main) thread is in no danger on being blocked initially, but it can eventually be blocked when the actual callback function is run.

As you can see from these examples, the work a higher-order and callback function do is what's critical to obtaining true asynchronous & non-blocking execution. So let's take a look at the type of work functions have to perform to be truly asynchronous and non-blocking.

Functions for UI (User Interface) and I/O(Input/Output) related work: Asynchronous & non-blocking

The primary characteristic of the work performed by the previous functions -- callbacks and higher-order -- is there are no interrumptions in the work they do. When the primeNumbersTo() function is called each time in listing 7-1, it completely takes over JavaScript's single(main) thread. Similarly, when the setTimeout() value is reached in listing 7-2, the longTask() function completely takes over JavaScript's single(main) thread. And a similar thing happens in listing 7-3, on each loop iteration the primeNumbersTo() function also completely takes over JavaScript's single(main) thread.

So is there any type of work that doesn't overtake JavaScript's single(main) thread and has natural pauses ? Yes, there are actually two types of work that are quite common in the context of browsers and thus JavaScript. One is for functions associated with a UI, where higher-order functions are charged with triggering a callback function once a certain thing happens on a UI (e.g. a click, a tap). Another is for functions associated with I/O related work (e.g. getting a response from a remote service, reading a file) where a callback function is triggered once a certain thing happens (e.g. a response is received, an error ocurrs). In both cases, waiting for a user to perform an action on a UI or waiting for an I/O operation to complete, is simply a waste of time, because there's no certainty when it will happen. So JavaScript allows you to incorporate a wait-and-see approach for functions with this type of work, without overtaking JavaScript's single(main) thread. All of this is possible due to JavaScript's event-loop design, which is explained in greater detail after exploring a couple of JavaScript snippets with UI and network related I/O work.

Listing 7-4 illustrates the same primeNumbersTo() function from previous examples, but this time set up as a UI callback function, so its activity is triggered until a user clicks on a button.

Listing 7-4. UI callback function
<html>
  <body>
    <h1>UI callback function</h1>
    <button maxValue="100">Primes up to 100</button><p/>
    <button maxValue="10000">Primes up to 10,000</button><p/>
    <button maxValue="10000000">Primes up to 10,000,000</button><p/>
    <button maxValue="10000000000">Primes up to 10,000,000,000</button>
    <p>
      Resulting primes: <div id="result">Waiting for click...</div></p>
    <script>
      console.log("About to add UI callbacks");
      const result = document.getElementById("result");
      const btns = document.getElementsByTagName("button");
      for (let k = 0; k < btns.length; k++) {
        btns[k].addEventListener("click", primeNumbersTo);
      }


      function primeNumbersTo(event)
      {
          const max = event.target.getAttribute("maxValue");
          var store  = [], i, j, primes = [];
          for (let i = 2; i <= max; ++i) 
          {
              if (!store [i]) 
                {
                  primes.push(i);
                  for (let j = i << 1; j <= max; j += i) 
                  {
                      store[j] = true;
                  }
              }
          }
          console.log("Primes from max %s: %s", max, primes);
          result.innerHTML = primes
          return primes;
      }

      console.log("Finished adding UI callbacks")
      console.log("I'm a really urgent log message");
    </script>
  </body>
</html>

See the Pen UI callback function - Modern JS by D Rubio (@modernjs) on CodePen.


The first section in listing 7-4 is an HTML page with several buttons with different value attributes assigned to each one. Next, is the JavaScript section with several log statements, as well as a couple of DOM operations to work with the buttons on the HTML page and the same primeNumbersTo() function from previous examples.

One of the most important behaviors in listing 7-4 is the last log message "I'm a really urgent log message" is output right away, without having to wait for any prime number calculations like listing 7-1. Next, are the document.getElementById("result") statement that's used to reference the HTML <div id="result"> element and the document.getElementsByTagName("button") statement to reference all HTML <button> elements.

Next, a loop is run over the btns reference that holds all the HTML <button> elements and an event listener is attached to each one with the addEventListener() method. The addEventListener() method is called with two arguments: the "click" argument, to indicate the action on the element that triggers a callback function; and the primeNumbersTo argument, to specify the actual callback function. At this juncture, all HTML <button> elements are set up to trigger the primeNumbersTo method as a callback when a user clicks on them.

The core logic of the addEventListener() function in listing 7-4 is identical to previous versions, however, it differs in its argument and a couple of lines. The primeNumbersTo() function in listing 7-4 accepts the event argument to indicate an event object input vs. a plain number max input. Next, in the first line of the function logic you can see the event argument provides access to the HTML element associated with the event (i.e. the click event) through the target attribute of the event. And it's through the getAttribute() method applied to the HTML element, you can access the maxValue attribute of the clicked HTML element to determine the number value to run the function's core logic. Finally, the second to last line in the primeNumbersTo() function updates the HTML element reference result with the calculated primes.

If you run the example in listing 7-4, you can see how JavaScript can defer the execution of a function by making it a callback that's triggered when a user takes a certain action. Before moving to the next example, it's important to point out that when you run the example in listing 7-4 to calculate prime numbers up to 10,000,000 and 10,000,000,000, the UI freezes and you aren't able to click on any button until the task finishes, this is because the function's logic takes over JavaScript's single(main) thread, the only solution to this problem is to place the callback function on a parallel thread (a.k.a. Web worker), a topic that's addressed in an upcoming section.

Following in the steps of this last UI driven activity, another common approach in JavaScript is to defer work tied to I/O activities through callback functions, where I/O refers to operations related to transferring data in many contexts (e.g. files, networks). While a user can trigger work by means of interactions (e.g. a click, a tap), I/O activities are more driven by system actions (e.g. getting a response from a remote service, reading a file), activities that also shouldn't overtake JavaScript's single(main) thread.

For our discussion on callback functions related to I/O activities, let's talk about AJAX. AJAX (Asynchronous JavaScript and XML) emerged as a means to get data into a browser from a remote server without blocking JavaScript's single(main) thread. The foundations for AJAX were set a long time ago through the XMLHttpRequest[1] object data type, which was never part of ECMAScript, but rather one of the first of many ad-hoc non-standard JavaScript techniques adopted by major browsers. As AJAX became a popular technique, easier solutions emerged to achieve the same results, some of these included libraries like jQuery adding AJAX methods and more recent developments like the Fetch API supported by most major browsers, the last of which we'll talk about shortly.

Early AJAX techniques (e.g. XMLHttpRequest, jQuery library) operate through the use of callback functions. In such cases, anything can trigger an AJAX call to a remote server asynchronously, while JavaScript's single(main) thread can continue uninterrupted and callback functions are assigned the duties to wait out and process the remote server's response. Listing 7-5 illustrates a simple AJAX sequence that uses the jQuery library.

Listing 7-5. jQuery AJAX call with callbacks
<html>
<body>
  <div id="banner-message">jQuery with callbacks</div>
  <h4>
    <button id="ajax-call">Click to update message via AJAX</button>
  </h4>
  <script>
      console.log("Start processing");
      var bannerMessage = $("#banner-message");

      $("#ajax-call").on("click", function (event) {
        $.ajax({
          url:
            "https://cors-anywhere.herokuapp.com/https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=JavaScript&format=json"
        })
          .done(function (data) {
            console.log(data);
            bannerMessage.html(
              `There are ${data.query.searchinfo.totalhits} JavaScript related articles on Wikipedia.`
            );
          })
          .fail(function (error) {
            console.log(`An error ocurred on the AJAX call: ${error}`);
          })
          .always(function () {
            console.log("Always called on an AJAX call");
          });
      });
      console.log("Finished processing!");  
  </script>
</body>
</html>

See the Pen jQuery AJAX call with callback - Modern JS by D Rubio (@modernjs) on CodePen.


The first section in listing 7-5 is an HTML page with a button to trigger an AJAX call and an HTML <div> element to display the results of the call. If you run the example, you'll notice the first log statement "Start processing" and last log statement "Finished processing!" are output first, so there's no interruption of JavaScript's single(main) thread on account of embedding an AJAX call.

The HTML button on the page is assigned an event listener through the jQuery reference $("#ajax-call"), where #ajax-call is jQuery's syntax to identify an HTML element with id="ajax-call". The jQuery .on() method is used to assign an event and function to execute on a given HTML element, in this case, it's a "click" event and a function whose core logic is an AJAX call. Note that jQuery's .on() is functionally equivalent to JavaScript's DOM addEventListener() method used in listing 7-4.

You can see the logic performed by the event listener function starts with the jQuery $.ajax("...") function that specifies a remote url among its arguments. Next, are three chained functions .done(), .fail() and .always(). These last functions are callbacks that handle the remote server response from the AJAX call. If the remote server returns a successful response .done is run, if the remote server sends a failed response .fail() is run, and irrespective of the remote server response .always() is always run.

As shown in listing 7-5, you can use callback functions for a variety of purposes, like running logic after a successful event, running them after a failed event or running them whenever an event runs irrespective of its outcome. But as helpful as callback functions can be, they can become too much of a good thing.

Callback hell

As simple as it has been shown in previous examples to integrate asynchronous behavior in JavaScript through callback functions, there's a major drawback with callback functions when you need to link multiple asynchronous tasks one afer the other. The drawback goes to the extent that it's often named callback hell[2], due to how difficult it can be to work with (e.g. understand, debug, change).

Building on the example in listing 7-5, listing 7-6 illustrates a series of nested AJAX calls in pseudo-code to better illustrate the problem with the use of callback functions.

Listing 7-6. jQuery nested callback functions in AJAX method call
// Operation 1 finishes here
// Operation 2 (AJAX) starts here 
$.ajax("/remoteservice/")
  .done(function(data) {
    console.log("success");
    $.ajax("/otherremoteservice/")
     .done(function(data) {
       console.log("nested success");   
       $.ajax("/yetanotherremoteservice/")
        .done(function(data) {
         console.log("nested nested success"); 
       });
   });
  });
// Operation 3 starts here, right away

As you can see in listing 7-6, once the first AJAX call completes successfully, it triggers yet another AJAX call, and once this last call completes successfully, it triggers yet another AJAX call. Notice this sequence doesn't even take into account error handling (i.e. the .fail() callback used in listing 7-5) or an always executed handler (i.e. the .always() callback used in listing 7-5). If an error happened on the first AJAX call, it would require an additional tree of calls, and to handle an error on the second AJAX call, yet another tree of calls. This type of callback nesting can quickly get out of hand, which is why it's often called callback hell.

If you think this type of operation nesting isn't too common in JavaScript, think again. With the demands placed on JavaScript applications, these series of dependent operations can easily be something like : an authentication service, that once successful loads a user's profile, that once successful loads a user's pictures or some other variation like it. Faced with these demands and to avoid confusing callback nesting, JavaScript introduced the Promise object data type.

Promises  

Promises are used for deferred and asynchronous operations. The need for JavaScript promises had existed for some time, to the point various JavaScript libraries emerged to fill in this void (e.g. WinJS[3], RSVP.js[4], Bluebird[5]). But it was only until ES6 that JavaScript got a native first level object named Promise for this purpose -- just like String, Date and Object. With Promise becoming part of the core language, the use of promises in JavaScript only rose, making it important to understand why and how to use promises.

While callback functions can be considered a type of promise, since they 'promise' to run after an event, promises avoid a lot of the baggage present in callback functions. Promise objects are passed around like any other object reference and are used to keep track of the state of a promise at any given time. The following example shows a function that checks if the input is a number and returns a Promise object as the result.

Listing 7-7. Promise object syntax
function promiseNumberCheck(argument) {
     if(isNaN(argument)) {
       return Promise.reject(`${argument} is not a number`); 
     } else { 
       return Promise.resolve(`We got number ${argument}`);
     }
}
let test1 = promiseNumberCheck(1);
let test2 = promiseNumberCheck("nothing");

test1.then(function(result) {
	console.log(result);
}, function(error) { 
  console.log(error);
});

test2.then(function(result) {
	console.log(result);
}).catch(function(error) { 
   console.log(error);
});

The promiseNumberCheck function in listing 7-7 uses isNaN to determine if the argument is a number. If the argument is not a number the function returns a Promise object as rejected with the given reason, if the argument is a number the function returns a Promise object as resolved with the given number. Next, two calls are made to the promiseNumberCheck function -- once with a number, the other with a string -- and the Promise objects results are assigned to the test1 and test2 references.

Next, you can see the Promise objects have the then() method called on them. The purpose of the then() method is to analyze the status of a Promise object. In this case, the status of both Promise objects is clear and instant because we set them, but in other cases a Promise object's status may be neither clear nor instant (e.g. if a remote service is involved). So the purpose of the then() method is to say 'when the Promise object status is set (rejected or resolved) and whether it's instantly or in 5 minutes, then do this'.

The Promise object's then method has two variations. The first option in the test1 reference uses two functions, the first one to handle a resolved promise and the second to handle a rejected promise, in both cases the function's input is the value assigned to the promise. The second option used in the test2 reference is to chain the then() method with the catch() method, in this case the then() method handles the resolved promise with a single method and the catch() method is used to handle a rejected promise with its own method.

Now that you're familiar with the syntax and main methods in Promise objects, let's create another example in listing 7-8 that introduces a delay in Promise objects to better illustrate how they're useful.

Listing 7-8. Promise objects with delay
function promiseRandomSeconds() { 
     let randomSeconds =  Math.floor((Math.random() * 10) + 1);
     return new Promise(
           function(resolve, reject) {
               // Perform logic and then determine to resolve or reject
               setTimeout(
                  function() {
                    if (randomSeconds%2 === 0) { 
                      // Resolve the promise if its even
                      resolve(`${randomSeconds} seconds`);
                    } else { 
                      // Reject the promise if its odd
                      reject(`${randomSeconds} seconds`);
                    }
               },randomSeconds*1000);
           }
     );
}
let test1 = promiseRandomSeconds();
let test2 = promiseRandomSeconds();

test1.then(function(result) {
	console.log(`Success: ${result}`);
}).catch(function(error) { 
   console.log(`Fail: ${error}`);
});

test2.then(function(result) {
	console.log(`Success: ${result}`);
}).catch(function(error) { 
   console.log(`Fail: ${error}`);
});

console.log("Hey there, I didn't have to wait!");

The promiseRandomSeconds() function in listing 7-8 first generates a random number between 1 and 10 to use as a delay. Next, it immediately returns a Promise instance with new Promise(). However, unlike the Promise objects used in the promiseNumberCheck() function in listing 7-7, the Promise object status in this example isn't determined until a later time, so the generic new Promise() syntax is used.

Notice that wrapped inside the new Promise() statement is the function(resolve, reject) { } function. It's inside this last function you can perform any required logic and then mark the Promise object with the resolve or reject references. In this case, the logic inside the function(resolve, reject) { } simply introduces a delay of promiseRandomSeconds -- the field value -- and depending on this value, marks the Promise object with resolve if the value is even or it marks the Promise object with reject if the value if odd.

Next in listing 7-8, two calls are made to the promiseRandomSeconds function and the results are assigned to the test1 and test2 references. Because the test1 and test2 references contain Promise object instances, the then() and catch() methods are then used to determine the status of both Promise objects, just it was done in listing 7-7.

The most interesting part in listing 7-8 is the last log statement console.log("Hey there, I didn't have to wait!"). Even though the logic prior to this log statement introduces potential delays between 1 and 10 seconds, this last log statement is the first thing that's sent to output. Because the prior logic is based on Promise objects, it doesn't interfere with JavaScript's single(main) thread and the final result (resolve or success) isn't reported until the Promise object determines it. In this case the Promise logic is pretty basic and based on a random number, but it can become a very powerful technique when used with tasks that can be delayed or return unexpected results (e.g. getting a response from a remote service, reading a large file).

Listing 7-9 illustrates an example that chains Promise objects.

Listing 7-9. Chained Promise objects
function promiseWordAppender(word) { 
    return Promise.resolve(word);
}

let test1 = promiseWordAppender("It's a Bird...");

test1.then(function(result) {
            console.log(result);
            return `${result} It's a plane...`;
           }).then(function(result) { 
            console.log(result);
            return `${result} No it's JavaScript Promises`;
           }).then(function(result) { 
            console.log(result);
           });

To simplify things, the promiseWordAppender function in listing 7-9 always returns a Promise object as resolved with the given word argument. The interesting functionality in this example comes with the chained then() methods on the test1 reference. Notice that inside the first then() method a return statement is used, this is a way to pass the result as the input to the subsequent method. Inside the second then() method, another return statement is used to pass the result as the input to the third and final then() method. In this case, the Promise logic simply appends words, but it's a powerful construct to be able to chain multiple then() statements with more complex logic (e.g. call service A, once finished call service B, once finished call service C), all this considering the times to execute each service can vary and are not guaranteed to be successful, not to mention it's far simpler to understand this syntax, than the one involving nested callbacks illustrated in listing 7-6.

To finish this section on Promise object data types, let's readdress the AJAX examples in listing 7-5 that uses callback functions to achieve asynchronous behavior. If you recall from that section, I mentioned the Fetch API[6] was one of the more recent solutions to address AJAX calls, as it turns out, the Fetch API is based on the concept of Promise objects. Listing 7-10 illustrates an example of how the Fetch API uses Promise objects to make AJAX calls.

Listing 7-10. Fetch API with Promises
<html>
<body>
  <div id="banner-message">fetch API with Promises</div>
  <h4>
    <button id="ajax-call">Click to update message via AJAX</button>
  </h4>
  <script>
      const bannerMessage = document.getElementById("banner-message");
      const ajaxButton = document.getElementById("ajax-call");

      ajaxButton.addEventListener("click", function () {
        fetch(
          "https://cors-anywhere.herokuapp.com/https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=JavaScript&format=json"
        )
          .then((response) => {
            console.log(response);
            return response.json();
          })
          .then(function (data) {
            console.log(data);
            bannerMessage.innerHTML = `There are ${data.query.searchinfo.totalhits} JavaScript related articles on Wikipedia.`;
          })
          .catch(function (error) {
            console.log(error);
          });
      });
  </script>
</body>
</html>

See the Pen Fetch API with Promises - Modern JS by D Rubio (@modernjs) on CodePen.


The first section in listing 7-10 is an HTML page with a button to trigger an AJAX call and an HTML <div> element to display the results of the call. Next, is the JavaScript section that first obtains references to the HTML <button> and <div> elements to add JavaScript logic to them.

The core of the logic in listing 7-10 is attached to the event listener on the ajaxButton button reference, which triggers an AJAX call when a click is made on the button. Unlike the event listener from listing 7-4, the event listener in listing 7-10 declares an in-line function with Fetch API logic. The fetch keyword is used to declare an AJAX call with the Fetch API, where the first argument declares the remote url to call. Next, you can see two Promise then() method calls. The first then() method logs and processes the response from the provided AJAX url as JSON, while the second then() method logs the JSON and updates the HTML element with the result. A minor difference between the two then() methods, although functionally equivalent, is the first one uses an arrow function, while the second an in-line function. Finally, a Promise catch() method is chained at the end, to handle any errors that might occur in the overall logic.

If you run the example in listing 7-10, you can see how JavaScript can perform AJAX calls with the Fetch API through the use of Promise objects vs. using callback functions like it's done in listing listing 7-5 with jQuery's AJAX support.

Async functions  

For all the effectiveness of Promises to achieve asynchronous behavior -- especially compared to callback functions -- JavaScript designers still saw room for improving the language's overall asynchronous support. In ES7 -- one version after Promises were introduced -- JavaScript added async functions that build on the concept of Promises and offer more clarity on where asynchronous behavior applies to workflows.

In previous examples you learned how easy it was to create and pass around Promise objects, inclusively, even to return Promise objects as the result of a function. However, because JavaScript is a Dynamically typed language, it's never easy to tell what data type value a function returns (e.g. String ? , Number ?, Promise ?). Because determining a function's return data type can be crucial to determining if it behaves asynchronously, the purpose of async functions is to identify and enforce functions to return Promise objects.

Async functions are declared by prefixing the async keyword to any function statement. Therefore, when you see the syntax async function() you know you're encountering a function that's designed to return a Promise object and in turn behave asynchronously.

In order to understand what async functions accomplish and what they don't, listing 7-11 shows our first async function, comparing it to a plain function used in previous examples.

Listing 7-11. Async functions compared to regular functions
function primeNumbersTo(max) {
  var store = [], i, j, primes = [];
  for (let i = 2; i <= max; ++i) {
    if (!store[i]) {
      primes.push(i);
      for (let j = i << 1; j <= max; j += i) {
        store[j] = true;
      }
    }
  }
  return primes;
}

// Start timer
console.time();
console.log("About to get primes");
let result = primeNumbersTo(25_000_000);
console.log(`result: ${Object.prototype.toString.call(result)}`);
console.log(`result: ${result.length} primes`);
console.log("Finished getting primes");
console.log("I'm a really urgent log message, I had to wait...");
// End timer
console.timeEnd();

console.log("------------");

async function asyncPrimeNumbersTo(max) {
  var store = [], i, j, primes = [];
  for (let i = 2; i <= max; ++i) {
    if (!store[i]) {
      primes.push(i);
      for (let j = i << 1; j <= max; j += i) {
        store[j] = true;
      }
    }
  }
  return primes;
}

// Start timer
console.time();
console.log("About to get async primes");
let asyncResult = asyncPrimeNumbersTo(25_000_000);
console.log(`asyncResult: ${Object.prototype.toString.call(asyncResult)}`);
console.log(`asyncResult: ${asyncResult.length} primes`);
// Evaluate Promise object from asyncResult
asyncResult.then(function(success) {
  console.log(`asyncResult (Success): ${success.length} primes`);
}).catch(function(error) {
  console.log(`asyncResult (Fail): ${error}`);
});
console.log("Finished getting async primes");
console.log("I'm a really urgent log message, I had to wait...");
// End timer
console.timeEnd();

The first half of listing 7-11 uses the same plain primeNumbersTo() function introduced in listing 7-1. Next, you can see a series of log statements and the invocation that results in the same blocking behavior you already knew about: The log statement "About to get primes" is output first; the primeNumbersTo() function is called to generate prime numbers up to 25,000,000, which takes over JavaScript's single(main) thread for ~5 seconds; a log statement outputs the object data type returned by the function -- an Array object -- as well as its size -- 1,565,927 primes in the Array; and finally the two log statements "Finished getting primes" & "I'm a really urgent log message, I had to wait..." are output after the function's ~5 second processing time.

The second half of listing 7-11 declares the asyncPrimeNumbersTo() function, which is identical in logic to the plain primeNumbersTo() function, except it's prefixed with the async keyword. So let's take a closer look at what changes and what doesn't when a function is made asynchronous with the async prefix.

Did you expect these results ? Surprised by any behavior ? Here's the recap:

Let's continue exploring async functions with another example, this time refactoring the Promise based example in listing 7-7 using an async function.

Listing 7-12. Async functions can reject and resolve promises
async function promiseNumberCheck(argument) {
     if(isNaN(argument)) {
       throw `${argument} is not a number`; 
     } else { 
       return `We got number ${argument}`;
     }
}
let test1 = promiseNumberCheck(1);
let test2 = promiseNumberCheck("nothing");

test1.then(function(result) {
	console.log(result);
}, function(error) { 
  console.log(error);
});

test2.then(function(result) {
	console.log(result);
}).catch(function(error) { 
   console.log(error);
});

The async function in listing 7-12 performs the same logic as the one in listing 7-7, but instead of explicitly returning a Promise object with a resolve or reject status, it simply returns either a plain string or throws an error. Because async functions automatically wrap their responses in Promise objects, errors thrown in an async function are treated as Promise.reject() responses, while all other responses are treated as Promise.resolve() responses.

Next, you can see two calls are made to the async promiseNumberCheck() function in listing 7-12 with the values 1 and "nothing", the responses of which are assigned to the test1 and test2 references. Because the responses are Promise objects, the then() method and catch() methods are applied to each Promise to analyze the status of the Promise object. If the Promise is resolved (e.g. the input was a number, like test1) the logic in the then() block is executed. If the Promise is rejected (e.g. the input was not a number, like test2) the logic in the catch() block is executed.

As you can see from these last two examples, async functions offer a great way to cut down on explicitly declaring Promise objects, as well as identifying functions designed to return asynchronous responses that need to be evaluated after a function is invoked.

Async functions with await  

Asyc functions represent the proverbial "kick the can down the road" approach to building workflows. Unsure how long a task will take ? Use an async function so it returns a Promise object right away, that you can evaluate later without interrupting JavaScript's single(main) thread. Pretty soon, you'll have many async functions passing around promises to each other and evaluating their state to fulfill an application's purpose. However, you'll eventually face a situation where immediately returning a Promise object from an async function won't work, this happens when a Promise object is dependent on another value/promise that also isn't yet determined. So how is it possible to return a Promise object with a value/promise that isn't yet determined ? You simply wait for it to be determined, by using the await keyword.

The await keyword is specifically designed to be placed inside the body of async functions, it doesn't work anywhere else. It tells an async function, "hold on, don't move from this line until you get a result back from it". This makes the await keyword ideal for tasks that depend on one another, because it ensures subsequent tasks have access to tasks that have been fully resolved.

Listing 7-13 illustrates the refactored version of the example in listing 7-8 using an async function and the await keyword.

Listing 7-13. Async functions with await
async function promiseRandomSeconds() {
  let randomSeconds = Math.floor((Math.random() * 10) + 1);
  await new Promise(r => setTimeout(r, randomSeconds * 1000));
  if (randomSeconds % 2 === 0) {
    return `${randomSeconds} seconds`
  } else {
    throw `${randomSeconds} seconds`
  }
}
let test1 = promiseRandomSeconds();
let test2 = promiseRandomSeconds();

test1.then(function(result) {
  console.log(`Success: ${result}`);
}).catch(function(error) {
  console.log(`Fail: ${error}`);
});

test2.then(function(result) {
  console.log(`Success: ${result}`);
}).catch(function(error) {
  console.log(`Fail: ${error}`);
});

console.log("Hey there, I didn't have to wait!");

The promiseRandomSeconds() function in listing 7-13 generates a random number between 1 and 10 to use as a delay, just like it's done in listing 7-8. However, it then makes use of the await keyword and the implicit use of Promise objects in async functions to achieve the same results.

The await new Promise(r => setTimeout(r, randomSeconds * 1000)); line creates a Promise object with the setTimeout() function to run after randomSeconds. The most important aspect of this line is the await keyword, which tells the async function to wait until the Promise object is resolved. In this case, the logic executed by the Promise is rather silly, it just adds a delay of x randomSeconds to the async function, but it could equally be more important logic like reading a file or receiving a response from a remote service, which are needed further down in the async function's logic.

Once the await new Promise(r => setTimeout(r, randomSeconds * 1000)); line is resolved, the async function continues its workflow and reaches the conditional where if the randomSeconds value is even it returns a plain string or if the value is odd it throws an error. Finally, you can see two calls made to the promiseRandomSeconds() function and the resulting Promise objects are evaluated with the then() method and catch() methods to analyze the status of the Promise object.

The use of the await keyword in listing 7-13 is very simple, so let's explore a more elaborate example that uses the await keyword to wait for an actual computation and data.

Listing 7-14 illustrates the refactored version of the example in listing 7-10 using an async function and the await keyword with the Fetch API to perform an AJAX call.

Listing 7-14. Fetch API with asnyc and await
<html>
<body>
  <div id="banner-message">fetch API with async and await</div>
  <h4>
    <button id="ajax-call">Click to update message via AJAX</button>
  </h4>
  <script>
      const bannerMessage = document.getElementById("banner-message");
      const ajaxButton = document.getElementById("ajax-call");

      async function fetchJavaScriptWikipediaArticles() {
        const response = await fetch(
          "https://cors-anywhere.herokuapp.com/https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=JavaScript&format=json"
        );
        const json = await response.json();
        return json;
      }

      ajaxButton.addEventListener("click", () => {
        fetchJavaScriptWikipediaArticles().then((articles) => {
          console.log(articles);
          bannerMessage.innerHTML = `There are ${articles.query.searchinfo.totalhits} JavaScript related articles on Wikipedia.`;
        });
      });  
  </script>
</body>
</html>

See the Pen Fetch API with async and await - Modern JS by D Rubio (@modernjs) on CodePen.


The most important change to the example in listing 7-14 is the bulk of the AJAX call is contained in the async fetchJavaScriptWikipediaArticles() function. Inside this function you can see the first step is using the fetch keyword to declare an AJAX call, more importantly though, notice how the statement is prefixed with await which forces the function to wait until a response is obtained. Next, is the await response.json() line, which converts the response from the first step into JSON and similarly uses the await prefix to force the function to wait until the conversion is made. Finally, the async fetchJavaScriptWikipediaArticles() function returns the result in JSON through the return json line.

Note the async fetchJavaScriptWikipediaArticles() function in listing 7-14 is added as part of the logic triggered when a click is made on the HTML button element. In this case, the event listener in listing 7-14 declares an arrow function and inside you can see a call is made to the fetchJavaScriptWikipediaArticles() function. Finally, since the function is async and returns a Promise object, it's chained to a then() method which takes the input from the async function to output the results in a log statement and update an HTML element with the results.

Web workers

The last topic in this chapter is parallel execution, which in JavaScript is achieved through Web workers. Parallel execution is a complex subject, due to the many possibilities that can arise when two tasks are running in parallel, like: Do tasks need to communicate what they're doing with one another ? Does one task need to finish before another can start ? If one task fails, does it influence another running task ? Every programming language deals with these issues in different ways and the complexity stems from the side-effects that can appear when data and logic from multiple tasks interact and run in parallel (i.e. simultaneously).

JavaScript deals with parallel execution by associating tasks to .js files or scripts. If you recall from the running JavaScript section in an earlier chapter, JavaScript code is typically placed in .js files so it can be run on JavaScript engines. By default, if you have ten .js files they're loaded and run sequentially (i.e. one after the other) by JavaScript's single(main) thread, leading to the potential blocking issues we've discussed so far. The loading of .js files into a JavaScript engine can actually be done asynchronously as discussed earlier, since being an I/O network task it can avoid blocking JavaScript's single(main) thread -- this topic is also discussed in the context of HTML pages in processing attributes for the HTML <script> element: async & defer. However, when it comes to running .js files, each .js file will overtake JavaScript's single(main) thread if not specified otherwise.

A Web worker operates by loading and running a .js file into a thread that operates independently from JavaScript's single(main) thread. So if you have a heavy computation -- like the prime numbers calculation from previous sections -- you can place its code in a separate .js file (e.g. primeNumbers.js) so the computation runs in a separate thread that doesn't block JavaScript's single(main) thread.

Sounds great, right ? Well, before you get too excited, there are some extra steps you need to take for this to work. The main one is communicating between JavaScript's single(main) thread and its Web worker(s). If you have the primeNumbersTo() function running on a Web worker, you can't simply call it with the same basic syntax you use for calling a regular function on JavaScript's single(main) thread, you must instead relay a message to the function running on a Web worker. Cool, you can relay messages, why not, right ? Well, the catch is the Web worker must implement another function to accept messages, which it then uses to invoke the actual function in the Web worker. In addition, the Web worker must also emit a message with a response when it finishes the work triggered by the first message. And to top it off, JavaScript's single(main) thread must also implement a function to process response messages from the Web worker.

So, the cost of using Web workers implies adding a series of scaffolding functions to deal with the movement of messages between JavaScript's single(main) thread and its related Web worker(s). The good news about these message handling functions is they follow in JavaScript's event-driven design. Since you don't want these functions to block waiting for messages to be processed or sent, these functions rely on the same JavaScript UI asynchronous approach to react when certain thing happens. In this case, the functions are designed to run when a message is sent or when a message is received, allowing both JavaScript's single(main) thread and its related Web worker(s) to continue going about their work without blocking for any given message to be relayed.

Although there are other costs and limitations of a lower impact to using Web workers -- which I'll explain shortly -- let's take a look at a first Web worker example. Listing 7-15 shows a Web worker example that calculates prime numbers up to a given number, based on the same primeNumbersTo() function from previous examples.

Listing 7-15. Web worker that calculates prime numbers
<html>
<body>
  <h1>Web worker that calculates prime numbers</h1>
  <button maxValue="100">Primes up to 100</button>
  <p/>
  <button maxValue="10000">Primes up to 10,000</button>
  <p/>
  <button maxValue="10000000">Primes up to 10,000,000</button>
  <p/>
  <button maxValue="10000000000">Primes up to 10,000,000,000</button>
  <p>
    Resulting primes:
  <div id="result">Waiting for click...</div>
  </p>
  <script>
      var primes = new Worker("/scripts/primeNumbers.js");

      console.log("About to add UI callbacks");
      const result = document.getElementById("result");
      const btns = document.getElementsByTagName("button");
      for (let k = 0; k < btns.length; k++) {
        btns[k].addEventListener("click", primeButton);
      }

      function primeButton(event) {
        const max = event.target.getAttribute("maxValue");
        primes.postMessage(max);
      }

      // Listen for a message from the worker
      primes.addEventListener("message", function (event) {
        result.innerHTML = event.data;
      });

      console.log("Finished adding UI callbacks");
      console.log("I'm a really urgent log message");
  </script>
</body>
</html>


// primeNumbers.js
this.onmessage = function (e) {
  console.log(`Message received from main script ${event.data}`);
  let primesUpTo = Number(event.data);
  primeNumbersTo(primesUpTo);
};

function primeNumbersTo(max) {
  var store = [],
    i,
    j,
    primes = [];
  for (let i = 2; i <= max; ++i) {
    if (!store[i]) {
      primes.push(i);
      for (let j = i << 1; j <= max; j += i) {
        store[j] = true;
      }
    }
  }
  console.log("Primes from max %s: %s", max, primes);
  this.postMessage(primes);
}
Note Due to the limitations of online JavaScript REPLs and Web worker security policies, the example in listing 7-15 can only run "as is" on a dedicated web server & domain. A later example showcases a modified version -- with an unnecessarily complex structure -- that's able to work in an online JavaScript REPL environment.
  1. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest    

  2. http://callbackhell.com/    

  3. https://msdn.microsoft.com/en-us/library/windows/apps/br211867.aspx    

  4. https://github.com/tildeio/rsvp.js    

  5. http://bluebirdjs.com/    

  6. https://developer.mozilla.org/en-US/docs/Web/API/fetch