Saturday, August 15, 2015

JavaScript Promises Part 2: JavaScript Event-based Concurrency

JavaScript is not a concurrent programming language. Not even close. Unlike languages such as C, the core language does not support thread or process creation. What this means is that there can never be two parts of the same JavaScript program running at the same time.

However, although the core language has no support for concurrency, it can implement concurrency through its host environments. One such host is the browser. To understand how JavaScript concurrency works in the browser and the role promises play, it helps to understand the basics of browser concurrency.

The browser is a concurrent system. If we think of the modern browsers job in terms of just computations, we can identify three primary, independent computations:
  • Making network calls (HTTP)
  • Handling external device input (keyboard and mouse)
  • Executing JavaScript (Chrome v8 Engine)
Internally, the browser is handling these computations and a range of other ones such as opening new windows and downloading files through the use of multiple threads and processes. Nearly all browser JavaScript runtimes expose an API for allowing programmers to use JavaScript to access the various internal browser capabilities. Therefore, all thanks to the browsers API, JavaScript is able to kick off network requests, set off timers, and handle device I/O. So even with the lack of concurrency support in the core language, the concurrent nature of the browser combined with its API makes it possible to implement concurrent applications with JavaScript.

As we mentioned in the previous post, implementing concurrency often means having to deal with the fundamental problem of synchronization. If there are multiple interacting threads or processes running in a concurrent system, there must be mechanisms for controlling their interactions. Even though JavaScript cannot spawn threads of its own through its own runtime, it still needs the ability to handle threads spawned by the browser. For example, when it triggers an HTTP request using the browser API, how does it know when it's finished?

The question we will address in this post of the series is: What does this synchronization mechanism look like in JavaScript? How does it work? Finally, in our next and last post we'll discuss how promises improve this mechanism and make our lives easier. 

To get a good sense of what the synchronization problem is in JavaScript and how it gets handled, we'll explore these three concepts:
  • Asynchronous I/O
  • Callback functions
  • The event loop
Asynchronous I/O

Whenever there are multiple threads or processes interacting in a concurrent system, that communication is either synchronous or asynchronous. Synchronous means that the thread which initiates another thread or process must wait for a response before continuing its own execution. Asynchronous means that it only initiates the operation but does not wait for it. 

In the browser, all interactions between the JavaScript thread of execution and other browser threads (timers, network calls, UI) are asynchronous. This means that concurrent procedures initiated by JavaScript do not block the execution of the program - those procedures are said to be asynchronous or non-blocking. 

This presents a seemingly impossible synchronization problem.

If concurrent operations running on separate threads don't block the execution of the program and the core language has zero support for dealing with synchronization, how is synchronization possible?

There must be some synchronization mechanism for controlling the interactions between an asynchronous operation and other operations in a program or else the best a JavaScript program can do is trigger a bunch of concurrent operations and go to sleep. Thankfully (not surprisingly), there is! The synchronization mechanism in JavaScript for handling concurrent operations relies on the use of callback functions.

Callbacks

Lets first look at an example of callbacks:

setTimeout(function () {
    console.log("Hello");
});
console.log("World!");

The anonymous function passed as the first argument to the setTimeout function is a callback function. A callback function is just a function passed to another function to be invoked by that function. It can either be an asynchronous callback function or a synchronous callback function. As you can probably guess, a synchronous callback is one that blocks the execution because it's immediately invoked. Synchronous callbacks aren't very interesting. Asynchronous callbacks, however, are key to synchronizing concurrent operations. 

In this case, we are passing an asynchronous callback function. We know it's asynchronous because setTimeout won't execute the function until five seconds into the future. In other words, it will only run in response to a specific event (in this case, the completion of the timer). When used in this way, the callback is also referred to as an event handler. Since the setTimeout operation is asynchronous, our code will continue executing. Since the timer is for 5 seconds, it will be a while until the event handler is invoked. 

Output:
> World!
> Hello
By supplying a callback to an asynchronous operation to be invoked when that operation is complete, we're able to synchronize the other thread used by the async operation with our main thread. Now, this is curious. How exactly does a JavaScript program know when an async operation is finished if it has zero support for concurrency? There's nothing in the language (or even the window API) that allows you to listen to a thread. Good question! Lets dive deeper. It's, again, thanks to the browser. The reason a callback based method of synchronization works at all is due to an event loop. 

Event Loop

The event loop is a queue system known as a message queue that runs as a separate thread in the JavaScript runtime. When an event occurs, the browser will check if an event handler is registered for that event. If so, it will add the handler as a message to the message queue. In our example above, the event is the timing out of the timer and the message is the function that prints "Hello". When the event occurs, the browser adds the event handler to the message queue as a message and that message then gets executed by the JavaScript engine once its call stack is empty. 

This implementation of concurrency using callbacks and event loops is known as event-based concurrency and it's a standard model across nearly all modern JavaScript runtime environments. In our next post, we'll talk about the problems that arise from synchronizing asynchronous functions through callbacks and, finally, why promises are a good solution. 

No comments:

Post a Comment