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 solution

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, also called blocking, sequential or synchronous, is JavaScript's default behavior as shown is listing 7-1.

Listing 7-1. JavaScript default synchronous behavior
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 stooping 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 2.5 to 4 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 2.5 to 4 seconds to appear.

One of the positives of this single-threaded and blocking 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 or that subsequent tasks lack data from prior tasks that haven't finished. 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 and blocking behavior is it creates a backlog of tasks that translates into delays. For things like a UI (User Interface) or an I/O operation like reading/writing a file, you don't want users experiencing a blocked/frozen UI while another task takes 2.5 or 10 seconds to finish, like you also don't want to stop everything for 2.5 or 10 seconds while a task reads or writes a large file.

There are two solutions to solving this single-threaded and blocking behavior, which allows tasks to be executed without having to wait for longer 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 is blocked until a response is received from the function. With asynchronous execution, the entire process is said to be non-blocking, because 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 block. So in listing 7-1, instead of making requests directly on the single(main) thread, you can place such requests to calculate prime numbers in a thread that runs in parallel to the single(main) thread. In this manner, the 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, asynchronously delaying the inevitable  

One of the earliest techniques to avoid JavaScript's single-threaded blocking behavior shown in the previous section, is to use timeouts to delay the execution of long lived tasks.

Although strictly speaking the use of timeouts produces asynchronous behavior, since it avoids blocking the single(main) thread, it does so delaying the execution of tasks that are still synchronous. In addition, there's no actual decoupling between the request and response of a task, but rather a decoupling of where a timeout is declared and when its time is complete to run a task, the actual request and response of the task are still done sequentially with the request blocking until a response is received.

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, immediately at least, JavaScript's single(main) thread.

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 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)).

Callbacks, they can be anywhere and they're not necessarily asynchronous

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-level or callback, as well as many things to act as a trigger for a high-level 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 primeNumber 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 primeNumber and call the primeNumbersTo() function.

In this case, the Array.forEach() method is the higher-level 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. This 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 2.5 to 4 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 -- is still synchronous and 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 a callback on each iteration over an array. Regarding the example in listing 7-2 and the longTask() callback function, what can be deceptive about it is the trigger for the higher-level function to invoke the callback is a delay, which defers the callback function to run at a later time and not block the JavaScript single(main) thread right away, but it eventually will block the single(main) since the callback function is synchronous.

As you can see from these examples, the contents of a function are what's critical to delivering on synchronous execution or asynchronous execution. So let's take a look at what it takes to make a function asynchronous.

Asynchronous callbacks, the early years

Callback functions are among the earliest and still widely used techniques to support asynchronous execution in JavaScript, for this reason, callback functions are often synonyms with asynchronous execution. However, this is a misconception, since JavaScript can have synchronous callbacks.

By the time ES5 became mainstream, callback functions were an established JavaScript development practice. ES5 itself added various new features that relied on the use of callback functions like Array.forEach[1], but the better known XMLHttpRequest[2] object data type -- which is technically not part of ECMAScript -- stormed the web and into every major browser as the foundation for AJAX (Asynchronous JavaScript and XML).

To start a concrete discussion on callback functions let's use AJAX, which is the technique you're most likely to have used in past JavaScript projects given its popularity. AJAX emerged as a means to get data into a browser from a remote server without the browser blocking. This means AJAX triggers a call to a remote server -- asynchronously -- while the main sequence can continue uninterrupted and callback functions are assigned the duties to wait out and process the remote server response. Listing 5-1 illustrates a simple AJAX sequence that uses jQuery library.

Listing 5-1. ES5: jQuery callback functions in AJAX method call
// Operation 1 finishes here
// Operation 2 (AJAX) starts here 
$.ajax("/remoteservice/")
  .done(function(data) {
    console.log("success");
  })
  .fail(function() {
    console.log("error");
  })
  .always(function() {
    console.log("complete");
  });
// Operation 3 starts here, right away

Notice in listing 5-1 that after operation 1 finishes, operation 2 with $.ajax starts and makes a call to a remote server. Because operation 2 is an AJAX call, it doesn't wait for the remote server to answer and the execution continues immediately to operation 3. So who or what handles the remote server response ? You can see that after the initial statement in operation 2 $.ajax("/remoteservice/") there are three chained functions .done(), .fail() and .always(). These last functions are callbacks and 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.

An important characteristic of callback functions is there's no assurance at what time they get called -- it can be 5, 10, 15 seconds or 1 hour later -- it all depends on how long the parent function takes to execute -- in this case whatever time the /remoteservice/ URL takes to send a response -- the key thing is other method calls in the workflow don't have to wait for the parent function to complete.

But as simple as it's to integrate asynchronous behavior in JavaScript through a callback function, there's a major drawback with callback functions when you need to link multiple asynchronous tasks one afer the other. Listing 5-2 illustrates a series of nested AJAX calls -- based on the snippet from listing 5-1 -- to better illustrate the problem with callback functions.

Listing 5-2. ES5: 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 5-2, 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 5-1) or an always executed handler (i.e. the .always() callback used in listing 5-1). 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, to the point it's often named callback hell[3], due to how difficult it can be to work with (e.g. understand, debug, change).

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, ES6 introduce the Promise object data type.

ES6: 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[4], RSVP.js[5], Bluebird[6]). 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 -- in the callback function example, a promise to process a remote server response -- 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 5-3. ES6: 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 5-3 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 5-4 that introduces a delay in Promise objects to better illustrate how they're useful.

Listing 5-4. ES6: 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
               window.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 5-4 first generates a random number between 1 and 10 to use as a delay. Next, it immediately return a Promise instance with new Promise(). However, unlike the Promise objects used in the promiseNumberCheck() function in listing 5-3, 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 5-4, 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 except() methods are then used to determine the status of both Promise objects, just it was done in listing 5-3.

The most interesting part in listing 5-4 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 the main sequence 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. a REST service, reading a large file).

Finally, to close the discussion on the Promise object data types, listing 5-5 illustrated an example that chains Promise objects.

Listing 5-5. ES6: 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 5-5 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, which is a way to invoke the parent method again. Inside the second then() method another return statement is used to call the parent method one more time. 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 5-2.

Asynchronous ES7: Async functions

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach?v=example#Syntax    

  2. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest    

  3. http://callbackhell.com/    

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

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

  6. http://bluebirdjs.com/