JavaScript Tutorial for Programmers - Async

October 13, 2017 0 Comments

JavaScript Tutorial for Programmers - Async

 

 

I still remember the days of debugging CORS problem when I put together some projects using JavaScript (& ajax), “a very particular programming language” in my first impression. Recently I got a great opportunity. The new role uses JS, the browser-side script that is now winning in all sides, as the major language. So I tooke it as a good chance to learn JS more systematically, and this series will be part of the outcome of my study. As the name implies, I will not cover primary level such as “if, else” (condition), “for” (or any kinds of loops), or basic OOP concepts. Instead, I will focus only on differences so you can learn this versatile language like reviewing a pull request, and use it the next day in your next awesome project.

Basically, asynchronization has two layers of meaning 1) unblocking of slow operations; and 2) triggering events non-linearly. In OS terms, the event is also called an interruption that can represent a coming network packet, a clock tick, or simply a mouse click. Technically, the event interrupts the current process, puts the next CPU instruction on hold, and calls a predefined code block (a.k.a., an event handler), “asynchronously”.

The concept is essentially the same in application level.

The problem of blocking operations

In a narrow sense, asynchronization solves a fundamental difficulty in application development: blocking operation (mostly I/O). Why blocking is difficult? Well, no matter what kinds of App (with UI) you are working on (an embedded system, an mobile App, a game, or a web page), there is a underlying “loop” that refreshes the screen in a very high frequency. If the “loop” is blocked by a slow operation, say, a network interaction, your UI will be frozen, and the users might just let the App go. In particular, JavaScript runs as part of the “loop”, so we need to wield this black magic wisely.

Before we start experimenting on JS, let’s do some preparations.

Firstly, we download Moesif Origin & CORS Changer because we are going to make (a lot of) cross-origin HTTP requests. (as briefly mentioned in my first post)

Secondly, we use python (Flask) to simulate a slow API which sleeps ten seconds for each request to impose noticeable latency:

if __name__ == "__main__":
app.run(host='***.***.***.***', threaded=True)

Now we toggle the CORS plug-in to “on” (otherwise the network request will fail instantly) and run the example:

<button type="button">Click Me!</button>
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://***.***.***.***:5000/lazysvr", false );
alert(xmlHttp.responseText);

By debugging the code, we can observe that that after the network request, the code is suspended at the following line:

for >10 seconds and the button is not clickable at all before it displays the result:

Moreover, the runtime (I’m using Chrome) complaints:

[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help, check https://xhr.spec.whatwg.org/.

which can be an official statement of the problem.

Asynchronization in action

Broadly speaking, asychronization can be 1) (slow) operations that are performed from another thread; or 2) events that are triggered from external; or the composite of both. I am introducing three examples to demonstrate asychronization in code:


The first one, a packet arrival

The code used by this example also can solve the problem discussed in the previous section:

<button type="button">Click Me!</button>
var xmlHttp = new XMLHttpRequest();
-- xmlHttp.open( "GET", "http://192.241.212.230:5000/lazysvr", false );
++ xmlHttp.open( "GET", "http://192.241.212.230:5000/lazysvr", true );
++ xmlHttp.onreadystatechange = function() {
++ if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
++ alert(xmlHttp.responseText);
-- alert(xmlHttp.responseText);

In this example, we 1) change the second parameter to “true” so as to offload the slow network interaction to another thread, and 2) register a callback as an event handler for the response packet. The callback will be effectively triggered from the other thread when the network interaction completes.

This time, the button can respond to a user’s click throughout the process and

is displayed after the send as expected.

The second, a clock tick

setTimeout(callback, 3000);
alert('event triggered');

N.b., 1, JavaScript does not allow synchronous sleep() from beginning.
N.b., 2, unlike OS kernel, the clock tick here will never trigger a process (thread) scheduling. As mentioned before, all the JavaScript code is running in one thread.

And the third, a mouse click

<button type="button" onclick="callback()">Click Me!</button>
alert('event triggered');

In all the three examples, we register handlers (a.k.a., callbacks) for certain events occurred outside of the main thread. In the first example, we also offload a slow operation to an external thread to fight the blocking problem. As mentioned, all operations can be abstracted in one word, asynchronization!

The new fetch() API

In the first example, packet arrival, I use a callback to make the operation more obvious as asynchronized. A better practice of sending network request is to use the newer API — fetch(). The function returns a Promise that can call then() in turn, so that

  1. the asynchronized operation can be coded in a synchronized manner (thus less obvious), and
  2. the so dubbed “callback hell” can be effectively avoided, and the best part
  3. all the potential exceptions involved in multiple asynchronized calls can be handled in one place:
<button type="button" onclick="callback()">Click Me!</button>
fetch("http://192.241.212.230:5000/lazysvr")
}).catch(function(error) {
console.log('error: ' + error.message);

The result is the same as the example one, and I leave the button there for you to click.

Under the hood, multi-threaded + event loop

The answer is yes and no. I’ll explain:

for (i = 0; i < 1000; i++) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://192.241.212.230:5000/lazysvr", true );
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
alert(xmlHttp.responseText);

Assuming the browser’s pid is 666, we can use a simple script (I’m using Mac) to monitor the status of threads belonging to the browser :

while true; do ps -M 666; sleep 1; done

initial values (I beautified the output a bit by removing the irrelevant columns and rows):

holmes 666 ... 0:00.42 0:01.47 ...

values when I stop:

holmes 666 ... 0:00.50 0:01.88 ...

Besides the main thread, there is another thread that is pretty active during the process, which indicate that one more thread is involved, most likely, by sending the network request and listening to the multiplex socket.

So JavaScript runs in a single thread indeed. But you take the perspective from the application, it is multi-threaded. Feel free to conduct the same experiment on other JavaScript platforms like Node.js.

Event loop, the coarse-grained asynchronization

I hope you still remember that asynchronized exception is triggered in a granularity of CPU instruction in OS level as I mentioned it in the beginning of this article. What about that in JavaScript?

We look at a frequent example first:

for (i = 0; i < 3; i++) {
alert('event triggered');

We know that the result is:

To recap, though we register a time event and indicate the callback should be invoked immediately, the runtime still waits for the current “loop” iteration to finish before it executes the callback from an “event queue”.

It means unlike other applications that can start responding to user input from next couple of CPU circles, JavaScript can only do that after a “loop circle”.

Does the coarse-grained event handling slow down the speed that an UI component can respond to a user’s operation? I think no. Even with the system level interruption, a user’s operation can be reflected on the UI after a “loop circle”, as UI updating can be only performed from the main thread. Thus, this simplified, single threaded event loop design itself does not impose penalties on UI performance. What do you think?

Conclusion

In this series, I have covered the diversified “equals to” operation and “null” value; as well as the simplified string, array, object and dictionary, in JavaScript. And I further discussed the object from a low level point of view, prototype in this and this post. And I highlighted “this” pitfall throughout the series in three different posts,

1st time
2nd time
3rd time

which signals its importance.

Then we come to this one that demystifies the asynchronization operation.


Tag cloud