Detailed explanation of event loop in javaScript --- event loop in browser and Node

Posted by gibbo101 on Mon, 27 Dec 2021 22:02:27 +0100

Event loops in javaScript

Processes and threads

Thread and process are two concepts in the operating system:

  • process: the program that the computer has run. It is a way of operating system management program;
  • thread: the smallest unit that the operating system can run operation scheduling. Usually, it is included in the process;

It sounds very abstract. Here is my explanation:

  • Process: we can think that starting an application will start a process (or multiple processes) by default;
  • Thread: in each process, at least one thread will be started to execute the code in the program. This thread is called the main thread;
  • Therefore, we can also say that a process is a container for threads;

Explain with another vivid example:

  • The operating system is similar to a large factory;
  • There are many workshops in the factory, which is the process;
  • Each workshop may have more than one worker in the factory, and this worker is thread;

Operating system – process – thread

How the operating system works

How does the operating system make multiple processes (listening to songs, writing code and looking up data) work at the same time?

  • This is because the operation speed of CPU is very fast, and it can quickly switch between multiple processes;
  • When the thread in our process gets the time slice, we can quickly execute the code we write;
  • This fast switching is not felt by users;

You can view many processes in the Mac activity monitor or Windows Explorer:

JavaScript threads in browsers

We often say that JavaScript is single threaded, but JavaScript threads should have their own container process: browser or Node.

Is the browser a process? Is there only one thread in it?

  • At present, most browsers are actually multi process. When we open a tab page, a new process will be started. This is to prevent all pages from being unable to respond due to a page jam, and the whole browser needs to be forced to exit;
  • There are many threads in each process, including threads executing JavaScript code;

The code execution of JavaScript is executed in a separate thread:

  • This means that JavaScript code can only do one thing at a time;
  • If this is very time-consuming, it means that the current thread will be blocked;

Therefore, the real time-consuming operations are not actually performed by JavaScript threads:

  • Each process of the browser is multi-threaded, so other threads can complete this time-consuming operation;
  • For example, for network requests and timers, we only need to execute the callback that should be available at the time of the feature;

Browser event loop

What if there are asynchronous operations during the execution of JavaScript code?

  • In the middle, we insert a function call of setTimeout;
  • This function is put into the call stack, and the execution will end immediately without blocking the execution of subsequent code;

Macro and micro tasks

However, not only one queue is maintained in the event loop. In fact, there are two queues:

  • Macro task queue: ajax, setTimeout, setInterval, DOM listening, UI Rendering, etc
  • microtask queue: Promise's then callback, Mutation Observer API, queueMicrotask(), etc

So what is the priority of the event loop for the two queues?

  • 1. The code in main script takes precedence (the top-level script code written);
  • 2. Before executing any macro task (not a queue, but a macro task), you will check whether there are tasks to be executed in the micro task queue
    • That is, before the macro task is executed, the micro task queue must be empty;
    • If it is not empty, the tasks in the micro task queue (callback) will be executed first;

Now let's practice through a few interview questions.

Promise interview questions

setTimeout(function () {
  console.log("setTimeout1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});

new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("then1");
});

setTimeout(function () {
  console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
  console.log("queueMicrotask1")
});

new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});

// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

promise async await interview questions

async function async1 () {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}

async function async2 () {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout')
}, 0)
 
async1();
 
new Promise (function (resolve) {
  console.log('promise1')
  resolve();
}).then (function () {
  console.log('promise2')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

Promise difficult interview questions

Promise.resolve().then(() => {
  console.log(0);
  // 1. return directly. A value is equivalent to resolve(4)
  // return 4

  // 2.return thenable value
  // return {
  //   then: function(resolve) {
  //     //A lot of calculations
  //     resolve(4)
  //   }
  // }

  // 3.return Promise
  // It's not an ordinary value. Add one more micro task
  // Promise.resolve(4), add one more micro task
  // Add two more micro tasks altogether
  return Promise.resolve(4)
}).then((res) => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() =>{
  console.log(6);
})


// 1.return 4
// 0
// 1
// 4
// 2
// 3
// 5
// 6

// 2.return thenable
// 0
// 1
// 2
// 4
// 3
// 5
// 6

// 3.return promise
// 0
// 1
// 2
// 3
// 4
// 5
// 6

Event loop for Node

EventLoop in the browser is implemented according to the specification defined in HTML5. Different browsers may have different implementations, while libuv is implemented in Node. (libuv is a library in Node)

Here we give an architecture diagram of a Node:

  • We will find that libuv mainly maintains an EventLoop and worker threads (thread pool);
  • EventLoop is responsible for calling other operations of the system: file IO, Network, child processes, etc

libuv is a multi platform library focusing on asynchronous IO. It was originally developed for Node, but now it is also used in Luvit, Julia, pyuv and other places;

Phase of Node event loop

As we emphasized earlier, the event loop is like a bridge, a channel connecting the JavaScript of the application and the system call:

  • Whether it is our file IO, database, network IO, timer or subprocess, after completing the corresponding operation, the corresponding result and callback function will be sent

    Put the number into the event loop (task queue);

  • The event loop will continuously take the corresponding event (callback function) from the task queue for execution;

However, a complete event cycle Tick is divided into many stages:

  • Timers: this stage executes the scheduled callback functions that have been setTimeout() and setInterval().

  • Pending Callback: performs a callback for some system operations (such as TCP error type), such as econnreused received during TCP connection.

  • idle, prepare: only used internally.

  • Poll: retrieve new I/O events; execute I/O related callbacks;

  • check: the setImmediate() callback function is executed here.

  • Closed callback functions: some closed callback functions, such as socket on(‘close’, …).

Phase diagram of Node event loop

Macro task and micro task of Node

We will find that the event cycle of Node is more complex than that of Tick, which is an event cycle. It is also divided into micro task and macro task:

  • Macro task: setTimeout, setInterval, IO event, setImmediate, close event;
  • microtask: Promise's then callback, process.nextTick, and queueMicrotask;

However, the event loops in the Node are not just micro task queues and macro task queues:

Micro task queue:

  • next tick queue: process.nextTick;
  • other queue: then callback and queueMicrotask of Promise;

Macro task queue:

  • timer queue: setTimeout,setInterval;
  • poll queue: IO event;
  • check queue: setImmediate;
  • close queue: close event

Sequence of Node event loops

Therefore, in the tick of each event loop, the code will be executed in the following order:

  • next tick microtask queue;
  • other microtask queue;
  • timer queue;
  • poll queue;
  • check queue;
  • close queue;

Node execute interview questions

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout0')
}, 0)

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')

// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2

Topics: Javascript