Event loops in Javascript

Javascript is a single-threaded, non-blocking scripting language. Single-threaded, i.e. at any time js code is executing, there is only one main thread to handle all tasks. Non-blocking, as long as it refers to the execution of an asynchronous task (such as an I/O event), the main thread will suspend the task, and then execute the corresponding callback when the asynchronous task returns the result according to certain rules.

The multithreading technology realized by Web worker technology also has many limitations. For example, all new threads are completely controlled by the main thread and cannot execute independently. This means that these ‘threads’ are actually children of the main thread. In addition, these child threads do not have permission to perform I/O operations and can only share some tasks such as computation with the main thread. So strictly speaking, Web workers don’t change the single-threaded nature of javascript.

  1. Execution stack and synchronous execution

The execution stack is different from the stack that stores object Pointers and underlying type variables. Execution stack refers to that when a method is called, JS will generate an execution context corresponding to this method, namely the execution context. The execution environment contains the private scope of the execution environment, the pointer to the upper scope, method parameters, private variables, and the this pointer to the scope. Because JS is single-threaded, only one method can be executed at a time, that is, when one method is executed, the other methods are queued to a separate place, the execution stack.

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. When a method is executed, JS adds the method’s execution environment to the execution stack, and then enters the execution environment to continue executing the code. When the code in this execution environment finishes executing and returns the result, JS will exit the current execution environment and undo the environment, and return to the execution environment of the previous method. This process is repeated until all the code in the execution stack is completed.

Case 1:

function Func1 () {

    console.log(1)

    function Func2 () {

        console.log(2)

        function Func3 () {

            console.log(3)

        }

        Func3()

      }

      Func2()

}

Func1()

/ / 1 2 3

Synchronous execution follows the rule of “first in, last out”. When Func1 is executed, the execution environment of this method will be added to the execution stack and output 1, and then the execution environment of Func2 will be parsed. When Func2 is executed, the execution environment of Func2 will be added and output 2, and then the execution environment of Func3 will be parsed and executed, and output 3. Then the execution of Func2 is completed and the execution environment of Func2 is cancelled, and finally the execution environment of Func1 is cancelled. This process, if not terminated, continues indefinitely until the stack overflows.

  1. Asynchronous execution

Method, the asynchronous execution event is suspended to a different queue from the execution stack, the event queue, and continues to execute other tasks in the execution stack. 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 for any tasks in the event queue. If so, the first event is taken and the callback for that event is placed on the execution stack, and then the synchronized code within it is executed, and so on, in an event loop.

Asynchronous task is divided into macro task and micro task because of the difference of tasks and execution priority.

Events belonging to macro tasks: setTimeout(), setInterval()

Events belonging to microtasks: New Promise(), New MutaionObserver()(abolished)

When the execution stack is empty, the main thread takes precedence to see if the microtask has an event. If not, the first event in the macro task is executed and the corresponding callback is added to the current execution stack; If so, the callback corresponding to the event in the microtask will be executed successively until the microtask queue is empty, and then the callback corresponding to the first event in the macro task will be executed again and again, and the loop will begin. Microtasks always take precedence over macro tasks in the same event loop.

Case 2:

setTimeout(function () {

    console.log(1);

});

new Promise(function(resolve,reject){

    console.log(2)

    resolve(3)

}).then(function(val){

    console.log(val);

})

/ / 2, 3, 1

Event loop in node environment

In Node, the event loop is slightly different than in the browser. The implementation of the event loop in Node relies on the Libuv engine. The Node uses Chrome’s V8 engine as the interpreter. After the V8 engine parses the JS code, it calls the Node API, which is driven by the Libuv engine. Therefore, the event loop in Node is executed in the Libuv engine.

In Node, after the synchronization code is executed, the microtask queue will be emptied first. In polling, all tasks in the current queue will be emptied before switching to the next queue. The microtask queue will also be emptied before switching to the next queue.

  1. Event cycle model



(from: node website)



  1. Event Loop Description

Node events loop in order:

External input data – > Poll Stage – > Check stage – > Close callback stage – > Timers – >I/O event callback stage – > IDLE, prepare – >poll…

setTimeout(() => {console.log(‘setTimeout’)} , 0)

setImmediate(() => {console.log(‘immediate’)})

By default, setTimeout() and setImmediate() do not know which mediate executes first, and Node does require lead time. Setting setTimeout() to 0 indicates that mediate mediate() does not mediate(), and the poll phase does not mediate(). The poll phase does not mediate(). Poll detects that the timer has arrived, and then executes setTimeout() in timer.

There is a special method in the queue, process.nexttick, to defer task execution. We know that each event loop starts with a microtask and that each stage is executed in the order of the event loop. Before each queue switch, the nextTick queue is checked to see if there are any events. If there are, the nextTick queue will be executed first.

Case 3:

setImmediate(() => {

    console.log(“setImmediate1”);

    setTimeout(() => {

        console.log(“setTimeout1”);

    }, 0);

});

setTimeout(() => {

    process.nextTick(() => console.log(“nextTick”));

    console.log(“setTimeout2”);

    setImmediate(() => {

        console.log(“setImmediate2”);

    });

}, 0);

/ / a as a result

// setImmediate1, setTimeout2, setTimeout1, nextTick, setImmediate2

2 / / results

// setTimeout2, nextTick, setImmediate1, setImmediate2, setTimeout1

The reason for these two results is the difference in node preparation time.

Case 4:

const fs = require(‘fs’);

fs.readFile(__filename, () => {

    setImmediate(() => {

        console.log(“setImmediate1”);

        setTimeout(() => {

            console.log(“setTimeout1”);

        }, 0);

    });

    setTimeout(() => {

        process.nextTick(() => console.log(“nextTick”));

        console.log(“setTimeout2”);

        setImmediate(() => {

            console.log(“setImmediate2”);

        });

      }, 0);

});

// setImmediate1, setTimeout2, setTimeout1, nextTick, setImmediate2

SetImmediate Does not mediate. SetTimeout2 and setTimeout1 are in the same Timers queue, so setTimeout2 and setTimeout1 are executed sequentially. Then switch to the check queue to execute setImmediate2. The nextTick queue will be checked and executed before switching, so nextTick, setImmediate2 will be printed at last

Note: Welcome to supervise and guide, if you have any questions or mistakes, please leave a message to discuss ~~