instructions

Try to answer a few questions:

  1. Why is JS a single-threaded language?
  2. How does JS handle synchronous and asynchronous tasks?
  3. The difference between macro and micro tasks
  4. Can browsers use JS engine threads to do the timer function? If not, how to achieve the timing function?

preface

JavaScript is a single-threaded language, mainly refers to the JS engine is single-threaded, as to why the JS engine is single-threaded? There should be no standard answer to this question, perhaps simply because of the complexity of multithreading, or because multithreading is generally locked, or because of interaction with the browser. So the original design was for a single thread.

Single-threaded means that at any point in your javascript code’s execution, there is only one main thread to handle all the tasks. What happens if you encounter asynchronous code? Will it block the current thread? Will it block if you encounter complex calculations? With these questions in mind, this article explores javaScript execution mechanisms.

Browser environment js engine event loop mechanism

The event loop involves several concepts:

  • JS engine thread
  • Event trigger thread
  • Timing trigger thread

Execution stack and event queue

Stack and heap

As javascript code executes, different variables are stored in different locations in memory: the heap and stack. There are some objects in the heap. The stack holds some basic type variables and Pointers to objects. But the execution stack we’re talking about here has a slightly different meaning than the one above.

Execution stack

When we call a method, JS generates an execution context corresponding to the method, also called the execution context. The execution environment contains the private scope of the method, the pointer to the upper scope, the method parameters, the variables defined in the scope, and the this object in the scope. When a series of methods are called in sequence, js is single-threaded and only one method can be executed at a time, so the methods are queued in a separate place. This place is called the execution stack.

Execute sync code

When a script is executed for the first time, the JS engine parses the code, adds the synchronized code to the stack in the order it is executed, and then executes it from scratch. If a method is currently executing, then JS will add the method’s execution environment to the execution stack, and then go into the execution environment to continue executing the code in it. Other methods, or even itself, can be called in this execution environment, which simply adds another execution environment to the execution stack. This process can go on indefinitely, unless a stack overflow occurs, that is, the maximum amount of memory available is exceeded. When the code in this execution environment completes and returns the result, js exits the execution environment and destroys the execution environment, returning to the execution environment of the previous method. This process is repeated until all the code in the execution stack has been executed.

Executing asynchronous code

Instead of waiting for an asynchronous event to return, the JS engine suspends the event and continues to execute other tasks in the stack. When an asynchronous event returns a result, JS adds the event to a different queue from the current stack, called the event queue. Instead of executing its callback immediately, it waits for all tasks in the current execution stack to complete, and when the main thread is idle, it looks up whether there are any tasks in the event queue. If so, the main thread will fetch the first event, place the corresponding callback on the stack, and execute the synchronization code. And so on and so on and so on and so on and so on and so on and so on and so on. This is why the process is called an Event Loop.

Macro Task and Micro Task

The above event loop process is just a macro representation, but in fact asynchronous tasks are different from one another and therefore have different execution priorities. Different asynchronous tasks are divided into two categories: micro tasks and macro tasks.

Micro tasks:

  • Promise.then, promise. catch, promise. finally
  • process.nextTick
  • MutationObserver

Macro task:

  • setInterval
  • setTimeout
  • setImmediate
  • I/O operations
  • requestAnimationFrame
  • ajax

In an event loop, asynchronous events return results that are placed in an event queue. However, depending on the type of asynchronous event, the event can actually be placed in the corresponding macro or microtask queue. And when the current stack is empty, the main thread will first check whether there are events in the microtask queue. If not, fetch an event from the macro task queue and add the corresponding event back to the current stack. If there is one, the microtask queue will execute the corresponding callback until the microtask queue is empty, then fetch the first event from the macro task queue and add the corresponding callback to the current stack. And so on and so on and so on.

In simple terms, when the current stack completes, all events in the microtask queue are processed immediately, and then an event is fetched from the macro task queue. Microtasks are always executed before macro tasks in the same event loop.

Here’s an example:

console.log(1);
setTimeout(function () {
    console.log(4);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val); }) the execution result is:1
2
3
4
Copy the code

In terms of threads:

  • Events in MacroTask are placed in an event queue, which is maintained by the event-triggering thread.
  • All microtasks in microTasks are added to Job Queues, which are maintained by JS engine threads, to wait for the current MacroTask to finish executing.

Let’s talk about timers alone

The core of event loop mechanism is: JS engine thread and event trigger thread. There are some hidden details, such as how after calling setTimeout, you wait a certain amount of time before adding to the event queue.

Is it detected by the JS engine? Of course not. It is controlled by the timer thread (because the JS engine is too busy to be anywhere else).

Why separate timer threads? Because JavaScript engines are single-threaded, it is necessary to have a separate thread for timing if it is blocked.

When will the timer thread be used? When using setTimeout or setInterval, it requires the timer thread to time, which pushes the specific event into the event queue.

Here’s an example:

// This code pushes the callback into the event queue to wait for the main thread to execute after 1000 ms is timed (by the timer thread)
setTimeout(function(){
    console.log('hello! ');
}, 1000);
Copy the code
// The effect of this code is to push the callback into the event queue as soon as possible, waiting for the main thread to execute
setTimeout(function(){
    console.log('hello! ');
}, 0);
console.log('begin');
​
// The result is as follows: begin followed by Hello!
// Although the code is intended to push the event queue after 0 ms, the W3C specification in THE HTML standard requires setTimeout to count intervals below 4ms as 4ms.
// Even if you do not wait for 4ms, even if you assume that 0 ms is pushed into the event queue, you will execute begin first (because the event queue is read only when the executable stack is empty).
Copy the code

SetTimeout and setInterval

SetTimeout will be executed every time when it is timed out, and then it will continue to be executed after a period of time, resulting in more errors (the error is related to the code execution time). SetInterval pushes an event at precise intervals (however, the actual execution time of the event may not be accurate, or the next event may come before the execution of this event is complete).

The fatal problem with setInterval is:

  • The cumulative effect (mentioned above), if the setInterval code has not completed execution before it is added to the queue again, causes the timer code to run several times in a row, with no interval between them. Even with normal intervals, multiple setIntervals may take less time to execute than expected (because the code takes a certain amount of time to execute). SetInterval is optimized by the JS engine. If there is a setInterval callback in the current event queue, it will not be added again.
  • In addition, setInterval does not stop executing programs when performing operations such as minimizing the browser display. Instead, setInterval puts setInterval’s callback functions in a queue, and when the browser window opens again, all the callback functions are executed in a flash.

Given the problems with setInterval, it is generally accepted that the best solution is to emulate setInterval with setTimeout, or directly use requestAnimationFrame in special cases.

conclusion

The javascrit event loop is an important and fundamental concept in this language. A clear understanding of the execution sequence of the event cycle and the characteristics of each stage can enable us to have a clear understanding of the execution sequence of an asynchronous code, thus reducing the uncertainty of code operation. The proper use of various methods of delaying events helps the code to better execute according to its priorities.