Today we’re going to talk about the event-loop in the browser. The Event Loop in the browser is a mechanism for coordinating and managing the order of execution of JavaScript code.


We first need to know that JavaScript is a single-threaded language. But what about understanding threads, we also need to understand processes first.


A process is the time it takes for the CPU to run instructions and save context, he is a relative description of the


A process is an instance of a running program in a computer. In an operating system, each process has its own memory space, resources, and state. A process can contain one or more threads that share the memory and resources of the same process.

 For example, a tab page is a process for a browser.


In modern operating systems, processes are multithreaded, which means that a process can execute multiple threads at the same time that share the same process’s memory space and resources.


We have talked about what happens from typing the url to rendering the page in the previous article. There are several threads in this process.


The main processes throughout the browser are: the GPU rendering thread, the http request thread, and the js engine thread. So a process consists of multiple threads working together, which creates an execution context, which is the memory space for execution. It takes multiple threads working together to complete the page presentation.


A thread is a smaller unit within a process that describes the time it takes for a segment of instructions to execute.


It is possible to cooperate between threads, but here we need to pay attention to.

 JS execution and html rendering cannot be done at the same time:


JS execution thread and html rendering thread these two threads can not be carried out at the same time. Because js is a language that can manipulate the DOM structure, if they can be done at the same time, assuming that both js and html are working on the same DOM structure, and one wants to set its width to 100px, and the other wants to set its width to 200px, it will result in unsafe rendering.

 To summarize.

 When the browser opens a new tab page, it is opening a new process

 Multiple threads need to work together to complete the page presentation, for example:

  1.  Render thread (GPU)
  2.  http request thread
  3.  js engine thread

 Render threads (GPU) and js engine threads are mutually exclusive

 js is a single-threaded language

 Why is js a single-threaded language?


This is a design problem with js, because back then js was designed to appear in the browser for some interactive functions, such as letting the user click buttons, so the language was designed as a scripting language for the browser. Multi-threading means high concurrency, high power consumption, and multi-threading means it must take up some more running memory. Use single-threaded to reduce the overhead.

  •  Save memory
  •  Save time on context switching


Context switching refers to the fact that in a multithreaded environment, when the operating system decides to stop the execution of the current thread and allocate CPU time to another thread, it needs to save the execution context of the current thread (e.g., the state of the registers, the stack pointer, etc.) and then load the execution context of the other thread. This process requires some time and overhead, and the frequent occurrence of context switching will affect the performance of the system. For example, in java there is the concept of , have a look.

 JavaScript Asynchronous


Asynchronous means that when code is executed, an operation does not return a result immediately, but returns the result at some point in the future. During this time, the code can continue to perform other tasks without having to wait for that operation to complete.

 And asynchronous in js is further categorized into macro tasks and micro tasks:


The main purpose of classifying asynchronous tasks into macrotasks and microtasks is to manage and control the order of their execution in order to improve the efficiency and performance of the program.

 Macro Task (macrotask)

  •  tab (of a window) (computing) script
  • setTimeout
  • setInterval

  • setImmediate : the specified dom structure is loaded and then executed, generally used in the framework of a lot of

  • I/O : Input-output events, such as a user clicking a button, are an input event

  • UI-rendering : page rendering, not much to do with the v8 engine

 microtask


  • promise.then() : Promise is synchronous, Promise.then is asynchronous microtasks

  • MutationObserver : Advanced methods in js for listening to the DOM tree.
  • Process.nextTick()

 event-loop

  1.  Execute the synchronization code
  2.  When the execution stack is empty, query if there is any asynchronous code to be executed
  3.  Implementation of microtasks
  4.  Renders the page if necessary

  5. Execute the macro task (this is also called the opening of the next round of event-loop)


One thing we should note is that executing asynchronous code is time-consuming, but time-consuming code is not necessarily asynchronous code.

setTimeout(() => console.log('setTimeout'), 1000)

for(let i = 0; i<10000; i++){
	console.log('hello world')
}


Here we for loop 10,000 times, or 100,000 times, the execution of so many times the loop is certainly time-consuming, but this time-consuming is determined by the performance of the computer, that is, the computer’s CPU, not the v8 engine. Assuming that the execution event of the for loop is 1s (the computer configuration of the high execution time is shorter), then the output setTimeout will need 2s, because the first execution of the synchronous code, and then execute the asynchronous code. for is a synchronous code, although it needs to be time-consuming.

 Now, let’s look at a code output question:

console.log('start');

setTimeout(() => {
    console.log('setTimeout');
    setTimeout(() => {
        console.log('inner');
    });
    console.log('end');
}, 1000)

new Promise((resolve, reject) => {
    console.log('Promise');
    resolve()
})
.then(() => {
    console.log('then1');
})
.then(() => {
    console.log('then2');
})


This kind of code output question is very common in the interview process, the interviewer will often use this kind of question to examine you an event loop mechanism. To do this kind of question, you must keep in mind the steps we mentioned above.


The code is executed in order from top to bottom. First, we come across console.log('start') , which is a piece of synchronization code, so we execute it directly and output start . Then we move down to the macro task setTimeout , which is not executed first, and put it into  , which is not executed in this round of event-loop. Then we come to Promise , which is also a synchronization code, so we output Promise . Continuing down the list, we find two .then , which are asynchronous microtasks, so we put them in the microtask queue.


Now, the synchronous code has been executed and the execution stack is empty (i.e., the code has all been processed and the asynchrony has been placed in the queue), and then go and check if there is any asynchronous code that needs to be executed. In the third step, we come to the microtask queue and start executing microtasks, so we output then1 , then2 in turn.

 The queue is FIFO, so the first in performs the exit first


Step 4, render the page, but there is no need to render the page here. So coming to the fifth step, execute the macro task in the macro task queue, also called start the next round of event-loop.


Check the macro task queue and find that there is a setTimeout among the macro tasks, take it out of the macro task queue and start execution.


Now we’ve come to the second event-loop, so we need to execute the synchronization code again, printing setTimeout . Then we’ll run into another one, setTimeout , and put it in the macro task queue. Then you run into the synchronization code, which prints end . At this point the execution stack is empty again, but we don’t have any microtasks to execute, so we go ahead and execute the macros, and the third event-loop is opened.


Take setTimeout out of the macro task queue and execute the synchronization code inner . There are no microtasks and the macro queue is empty, so the code is executed.

 Let’s take a look at the output.

async/await


This type of output question is not only promise.then , but sometimes async/await , and the two of them are going to be a little bit different.


async/await is meant to simplify the syntax of asynchronous operations in JavaScript, making asynchronous programming more intuitive and easier to understand. If asynchrony is a lot, the promise.then call is not that elegant, it needs to be written row by row, as in chaining:

function A(){
    return new Promise((resolve, reject) =>{
        setTimeout(() => {
            console.log('A fulfill')
            resolve()
        }, 1000)
    })
}
function B(){
    return new Promise((resolve, reject) =>{
        setTimeout(() => {
            console.log('B fulfill')
            resolve()
        }, 500)
        
    })
}
function C(){
    setTimeout(() => {
        console.log('C fulfill')
    }, 100)
}
A()
.then(() => { 
    return B()
})
.then(() => {
    C()
})


As above, we use promise.then() chain call to solve an asynchronous problem, and finally output asynchronous A completed, asynchronous B completed, asynchronous C completed. But if we have to write a lot of .then when there are a lot of asynchronies, it’s not elegant.

 Hence async/await was created.


function A() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('A fulfill');
            resolve()
        }, 1000)
  })
}

function B() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('B fulfill');
            resolve()
        }, 500);
    })
}

function C() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('C fulfill');
            resolve()
        }, 100)
    })
}

async function foo() {
    await A()  
    await B()
    await C()
}

foo()

 Similarly output asynchronous A complete, asynchronous B complete, and asynchronous C complete.


However, to use await , you must add the async tag to the function, async can appear alone, but await cannot appear alone.


The browser will give await a bootstrap, and the code following the await will be executed immediately. And awiat will block the subsequent code, pushing it into the microtask queue:

async function foo() {
    await A()  
    console.log(1);
    await B()
    await C()
}

foo()


See, we add a synchronized code print after the first await , so since await will block the subsequent code, it pushes console.log into the microtask queue


So print asynchronous A complete, 1, asynchronous B complete, and asynchronous C complete.

 Let’s look at a combination question:

console.log('script start')
async function async1() {
    await async2() 
    console.log('async1 end')
}
async function async2() {
    console.log('async2 end')
}
async1()
setTimeout(function () {
    console.log('setTimeout')
}, 0)
new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function () {
    console.log('promise1')
})
.then(function () {
    console.log('promise2')
})
console.log('script end')


Let’s analyze, first of all, the first execution of the synchronization code, print script start , the code continues to execute down to the async1 call, found a string of code await async2() , we said above, encountered awiat after the code, direct execution. So it prints async2 end . But wait below console.log(async1 end) will be pushed into the microtask queue, because await will block the code behind.


Then you come across the macro task setTimeout and push it into the macro task queue. You see Promise , which is a synchronization code, and output Promise . Then it encounters two .then and pushes them into the microtask queue. Then print the synchronization code script end .


At this point, the execution stack is empty and microtask execution begins. Output async1 end , promise1 , promise2 in order.


Next take out the macro task and start the second event loop by printing setTimeout .


There is another way to understand this, which I think is also OK. Since async/await is converted from promise, we can replace the code of async/await with promise to understand it:

console.log('script start')

function async1() {
    new Promise((resolve) => {
        console.log('async2 end')
        resolve(undefined)
    }).then(res => {
        console.log('async1 end')
    })
}
async1()

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

new Promise(resolve => {
    console.log('Promise')
    resolve()
})
.then(function () {
    console.log('promise1')
})
.then(function () {
    console.log('promise2')
})

console.log('script end')


After switching to Promise, do you feel that it has become a bit more friendly. This is mainly dependent on the special features of await . The result is the same after the conversion.

By hbb

Leave a Reply

Your email address will not be published. Required fields are marked *