primers

A few days ago, we had a discussion about JavaScript callback functions in our school group. One of our students proposed a point of view: callback functions are executed asynchronously!

When I saw this, I thought about the situations in which I had used callback functions, which were really asynchronous, and for a moment he made a lot of sense.

Of course, the statement itself, of course, is false, but based on the basic fact that functions in JavaScript, as first-class citizens, can be defined anywhere, inside or outside a function, as arguments and return values to a function, you can write higher-order functions.

A function that takes or returns a function is called a higher-order function

Commonly used built-in high-order functions such as Array object forEach,map, function composition, function currification, callback mode are all high-order functions. In fact, JavaScript is capable of functional programming (the FED article on functional programming), which I won’t go into here.

Getting back to that point, and obviously, going through an array with forEach is not asynchronous, so why is it so easy to associate asynchracy with callbacks? This question got me thinking, what are the origins of callbacks and asynchrony?

Single-threaded JavaScript

Speaking of asynchrony, let’s talk about the JavaScript runtime mechanism first. As we know, JavaScript is single-threaded, meaning only one task is running at any one time. Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever.

JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change.

Why asynchrony?

The advantage of single thread is relatively simple to implement, the execution environment is relatively simple; The disadvantage is that as long as one task takes a long time, subsequent tasks must wait in line, which will delay the execution of the entire program. A common browser non-response (suspended animation) is usually the result of a single piece of Javascript code running for so long (such as an infinite loop) that the entire page gets stuck in one place and no other task can be performed.

To solve this problem, the Javascript language divides the execution mode of the task into two modes: Synchronous and Asynchronous.

There are many good solutions for asynchronous processing. For a comprehensive understanding of how to handle asynchrony, @xiao Ji has written a very comprehensive article about asynchrony solutions in JS.

JavaScript execution mechanism

Before we talk about asynchrony, let’s talk about the JavaScript execution mechanism

function foo () {
    return foo();
}
foo();

// Uncaught RangeError: Maximum call stack size exceeded
Copy the code

This code raises an error indicating that the maximum call stack size is exceeded. What is the call stack?

Call Stack: The main thread that executes JavaScript is divided into heap and stack, and stack is an execution environment context.

A stack is a data structure in which data is first in and then out, then in and first out. So does the Call Stack, which executes JavaScript.

You can see the change in the call stack in the example above

Main (js file can be viewed as a main function) -> printSquare(square is called internally, so it needs to be pushed onto the stack) -> square(multiply is called internally, and pushed onto the stack) -> multiply

Multiply -> square -> printSquare -> printSquare -> printSquare -> printSquare -> printSquare

The Call Stack also has a maximum limit. You can use the following code to test the maximum call stack size of your browser


var i = 0;
function inc() {
    i++;
    inc();
}
inc();
//VM202:2 Uncaught RangeError: Maximum call stack size exceeded
i // 15720

Copy the code

Understanding JavaScript function calls is very helpful for understanding recursion, high-order functions, asynchronous functions, etc.

In the case of recursion, a recursive function that keeps calling itself pushes functions into the Call stack until the recursion condition is reached (at which point the function no longer calls itself) and then executes the functions in the stack on a last-in, first-out basis.

Asynchronous implementation

I can understand asynchronous implementation in three parts: webApi, task queue and Event loop

webApi

An example of asynchronous tasks in JavaScript, now limited to browsers, yields the following results:

  1. Dom events

  2. Timer setTimeout and setInterval

  3. XMLHttpRequest

As you can see, these are all browser apis, web apis. In fact, asynchronous implementation is handled by the browser, the main thread does not care how asynchronous implementation.

In fact, browsers are multi-process, so multiple threads can be opened to handle asynchronous behavior and synchronize to the task queue when the task completes

Task queue

The function specified by setTimeout is output after 0ms, but executed last

console.log(1);
setTimeout(() => {console.log('after 0ms')} ,0);
console.log(2);
console.log(3);

// 1
// 2
// 3
// after 0ms
Copy the code

Since the setTimeout function passes through webApi, after 0ms the timer executes and puts the callback function to the Task Queue. When the code in the Call stack finishes executing, the main process keeps checking the tasks in the Task Queue and takes out any tasks and puts them into the Call stack for execution.

The timing of setTimeout is inaccurate because the callback of the timer is waiting in the Task Queue while the current call stack executes

The same principle applies to other asynchronous apis, such as DOM events, Ajax requests, etc. When the asynchronous event completes, the corresponding callback function is placed in the Task Queue.

Tasks in a Task Queue need to poll repeatedly to see if any tasks have completed, and this poll is called the Event loop

Event loop

The Event loop is often implemented in a manner similar to the following


while (queue.waitForMessage()) {
  queue.processNextMessage();
}

Copy the code

If there are no messages queue.waitForMessage waits for synchronization messages to arrive.

Asynchrony and callback

At this point, the relationship between asynchrony and callback is clear.

Asynchronous: Create asynchronous tasks using webApi. When the task is complete, if a callback function is specified, the callback function is added to the Task Queue. If no callback function is specified, the event is discarded.

Callback function: defines the operation to be performed when the asynchronous task completes, including the event and the asynchronous task specified by the timer.

Code that avoids synchronization blocking

Tasks such as deep loops and synchronous Ajax requests can be time-consuming, and while the main thread has code executing, the code in the Task Queue stays in a waiting state, and the browser can’t interact or operate, and the page hangs.

Asynchrony in nodeJS

Node, as an event-driven, non-blocking IO, also uses an event loop. However, there are many differences between Node and browser implementations, so next time you can revisit the node article

Refer to the article

Help, I’m stuck in an event-loop

Concurrency model and event loop

If you have questions or suggestions for this article, you can contact me at [email protected]





Creative Commons Attribution – Non-commercial Use – Same way Share 4.0 International License