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.

Nothing new under the sun

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:

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask
import time

app = Flask(__name__)

@app.route("/lazysvr")
def recv():
time.sleep(10)
return "ok"

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head>
</head>
<body>
<button type="button">Click Me!</button>
<script>
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://***.***.***.***:5000/lazysvr", false ); // false for synchronous request
xmlHttp.send( null ); // the thread is suspended here
alert(xmlHttp.responseText);
</script>
</body>
</html>

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

1
xmlHttp.send( null ); // it is the aforementioned blocking operation

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

1
ok

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

1
[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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
</head>
<body>
<button type="button">Click Me!</button>
<script>
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 ); // 1) change the param to "true" for asynchronous request

++ xmlHttp.onreadystatechange = function() { // 2) add the callback
++ if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
++ alert(xmlHttp.responseText);
++ }
}

xmlHttp.send();
-- alert(xmlHttp.responseText);
</script>
</body>
</html>

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

1
ok

is displayed after the send as expected.

The second, a clock tick

1
2
3
4
setTimeout(callback, 3000);
function callback() {
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

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
</head>
<body>
<button type="button" onclick="callback()">Click Me!</button>
<script>
function callback() {
alert('event triggered');
}
</script>
</body>
</html>

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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
</head>
<body>
<button type="button" onclick="callback()">Click Me!</button>
<script>
fetch("http://192.241.212.230:5000/lazysvr")
.then((response) => {
return response.text();
}).then((text) => {
alert(text);
}).catch(function(error) {
console.log('error: ' + error.message);
});
</script>
</body>
</html>

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

Isn’t JavaScript single threaded

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

1
2
3
4
5
6
7
8
9
10
11
12
  var i;
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);
}
} // end of the callback

xmlHttp.send( null );
}

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 :

1
2
#!/bin/bash
while true; do ps -M 666; sleep 1; done

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

1
2
3
4
USER     PID ... STIME    UTIME
holmes 666 ... 0:00.42 0:01.47 ...
...
666 0:00.20 0:00.64

values when I stop:

1
2
3
4
USER     PID ... STIME    UTIME
holmes 666 ... 0:00.50 0:01.88 ...
...
666 0:00.37 0:01.28

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:

1
2
3
4
5
6
7
8
9
var i;
for (i = 0; i < 3; i++) {
alert(i);
}

setTimeout(callback, 0);
function callback() {
alert('event triggered');
}

We know that the result is:

1
2
3
4
1
2
3
event triggered

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

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.