Browser Thematic series – Event loops

preface

Let’s start with a brief talk about JavaScript and other things related to this topic to deepen the reader’s absorption and understanding of the content

Interpretive language

Scripting languages are computer programming languages created to shorten the traditional edit-compile-link-run process

Scripting languages (C/C ++, Java) are often called interpreted languages because their code is interpreted line by line rather than compiled

So javascript, like Python and the shell, is an excellent interpreted language

One of the performance bottlenecks for interpreted languages is the interpreter, and javascript has excellent interpreter engines such as V8 (Android, Chrome) and JSCore (IOS, Safari), which have contributed to the widespread adoption of JS

Single threaded model

One of the hallmarks of the javascript language is single-threaded, which means you can only do one thing at a time

Why single thread?

As a browser scripting language, javascript’s primary purpose is to interact with users and manipulate the DOM, which means that it has to be single-threaded, or it can cause complex synchronization issues

For example, if you have two threads of javascript, one thread adds content to a DOM node, and the other thread removes it, the browser doesn’t know which thread to use.

So, to avoid complexity, javascript has been single-threaded since its inception

Advantages of single threads

  • There are no deadlocks caused by contention between threads for resources
  • All code is executed synchronously
  • There is no resource overhead of thread switching

Disadvantages of single threading

  • 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

Task queue

There are scenarios in browsers where there are many time-consuming tasks, such as Network requests (Ajax), listening for event delivery, timers, and so on

The designers of the javascript language realized that the main thread could simply ignore the IO device, suspend the pending task and run the next one first. Wait until the IO device returns the result, then go back and continue the pending task

So all the tasks are divided into synchronous tasks and asynchronous tasks

Synchronization task

A queued task on the main thread can be executed only after the previous task is completed

Sequential execution

Asynchronous tasks

A task that does not enter the main thread but enters the task queue

An asynchronous task is executed on the main thread only when the task queue notifies the main thread that it is ready to execute

When the interpreter engine encounters an asynchronous task, it suspends it and, when the time is right, puts its callback function on the task queue

For example, like a stir-fry, the contents of the wok are constantly being stir-fried (the main thread), and various ingredients (different asynchronous tasks) are only added to the wok as needed to fulfill their mission

See the Browser theme series – Browser Kernel for more information on how the engine is made up

Asynchronous mechanisms

  1. All synchronization tasks are executed on the main thread, forming an execution stack
  2. In addition to the main thread, there is a task queue in which an event is placed whenever an asynchronous task has a result
  3. Once all synchronization tasks in the execution stack have been executed, the system reads the “task queue” to see what events are in it. Which corresponding asynchronous tasks, then end the wait state, enter the execution stack, start execution

Whenever the main thread is empty, it reads the “task queue”, that’s how javascript works. The main thread repeats step 3 above

Events and callbacks

A “task queue” is a queue of events. When an IO device (mouse, keyboard, etc.) completes a task, an event is added to the “task queue”, indicating that the related asynchronous task can be added to the “execution stack”. The main thread reads the “task queue”, which is to read what events are in it

As long as the callback function is specified, these events (mouse clicks, keyboard keystrokes, page scrolling, etc.) occur in a “task queue” waiting for the main thread to read

The “callback functions” are those that are suspended by the main thread. Asynchronous tasks must specify a callback function, which is executed when the main thread starts executing an asynchronous task

Queues have a first-in, first-out feature, and the main thread reads the first event in the task queue first

The main thread is read automatically. As soon as the stack is empty, the first position in the task queue is automatically sent to the main thread

For timer events, the main thread checks the execution time and returns to the main thread only after a specified time, that is, after a certain period of time, the corresponding callback function of the event is added to the execution stack

Event loop

What is an Event Loop

This is commonly known as the event loop

Event Loop is an execution model with different implementations in different places (in different languages)

The js event loop is responsible for executing code, collecting and processing events, and executing subtasks in the queue, which is quite different from other language models

One of the most interesting features of js’s event loop model compared to many other languages is that it never blocks, and processing I/O is usually performed through events and callbacks

So while an application is waiting for an AJAX request to return, it can still handle other things, such as user input, mouse clicks/scrolls, etc

What is an execution stack

The execution stack can be thought of as a stack structure for storing function calls, following the principle of first in, last out

Js will first create a main function when executing the code, and then according to the code executed, according to the principle of “first in, last out”, the last function will be the first to pop the stack

Here is an online tool for visualizing the execution stack -> Loupe

The sample

function a(v){
    return v*4
}
function b(v){
    return a(v*3)}console.log(b(2))
Copy the code

Into the stack order

1. main()
2. console.log(b(2))
3. b(2)
4. a(6)
Copy the code

The stack order

1. a(6)  / / 24
2. b(2)  / / 24
3. console.log(b(2)) / / 24
4. main()
Copy the code

When using recursion, there is a limit to how many functions can be stored on the stack. If too many functions are stored and not released, the stack will burst (as shown in the figure below).

Event Loop in browser

Through the above elaboration, probably also know how js is executed, understand how to process asynchronous tasks in the way of single line step execution mechanism, the following begins to describe the process of execution in detail

Is when you execute javascript code to implement the stack into the function/callback function, when faced with asynchronous code, can be hung and when you need to perform the join the task queue (many), once the execution stack is empty, the mechanism of time cycle Will take out need to execute code from the task queue and executed in the execution stack

So asynchrony in JS is essentially still a synchronous behavior

Task source

Different task sources are grouped into different task queues

Micro tasks

Micro tasks (microtask) : jobs

  • promise
  • MutationObserver
  • .

Macro task

Macro task (macrotask) : the tasks

  • script
  • xhr
  • setTimeout
  • setInterval
  • requestAnimationFrame
  • I/O
  • UI rendering
  • .

Event Loop Execution sequence

This first throws up the order of execution of the different tasks in the browser’s JS event loop

Each iteration of an event loop is called a tick

  1. Execute all synchronization code
  2. After all synchronous code is executed, the execution stack is empty. Then, check whether asynchronous tasks need to be executed
  3. performMicro tasksIf another microtask is generated during the execution of the microtask, it will be added to the end of the microtask queue and will be called to execute in this cycle
  4. After performing all the microtasks, render the page if necessary:
    • Determine whether the document needs to be updated
      • Most display devices still have a refresh rate of 60Hz, so it takes 16.6ms to update a render
    • Check whether there is a resize or scroll event. If there is, the event will be triggered
      • Therefore, resize and Scroll events will be triggered at least once in 16.6ms, that is, with throttling function.
    • Determine whether a media query is triggered
    • Update the animation and send the event
    • Check whether a full-screen operation event exists
    • Execute the requestAnimationFrame callback
    • The IntersectionObserver callback is executed to determine whether elements are visible and can be used for lazy loading
    • Update the interface
  5. Start the next Event Loop, take one execution from the macro task, and then the micro task…

Summary induction

  • A macro task executes one task at a time from the macro task queue, and then executes the tasks in the microtask queue
  • All tasks in the microtask queue will be taken out and executed until the microtask queue is empty.
  • When performing UI rendering, the time node is after performing all microtasks and before the next macro task
  • Timers are not infallible
    • SetTimeout/SetInterval simply puts its callback function into the macro task queue after the specified time

The sample

Here is an example to illustrate the sequence of code execution

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')
Copy the code
  1. Perform synchronous code outputscript start
    • console.log(‘script start’)
  2. Execute synchronous code output in async1async2 end
    • async1()
    • async2()
      • Because the function has the async flag, it returns a Promise, which we’ll call P1
    • console.log(‘async2 end’)
    • await
      • Await is an indication of a thread giving away an await async2() and then returning outside async1
      • “Await” is the syntactic sugar of generator plus Promise, and automatic execution generator is implemented internally, so there is another layer of Promise, which we call P2 and which is wrapped with P1
  3. When we encounter an asynchronous task timer, we suspend it and call it S1
  4. Execute the new Promise constructor and printPromise
    1. console.log(‘Promise’)
    2. resolve()
    • The generation of new microtasks is denoted as P3
  5. Perform synchronous code outputscript end
  6. At this point, all synchronous code execution is completed, and the situation of microtask and macro task queue is respectively
    1. Micro tasks: [P2, P3)
    2. Macro task: (S1)
  7. Perform all microtasks
    1. Take out P2 and execute it to generate new microtask P1 and add it to queue [P3,P1]
    2. Take out theP3Execute, print outpromise1And generate new microtasksP4Join a queue [P1.P4]
    3. Take out P1 and execute without output, generate new microtask P5 and add it to queue [P4,P5]
    4. Take out P4 execute, outputpromise2, no new tasks are generated [P5]
    5. Take P5 to execute, outputasync1 end, no new task is generated []

The result for the lower version browser is

// script start --> async2 end --> Promise --> script end --> promise1 --> promise2
// async1 end --> setTimeout
Copy the code

Since await is followed by Promise, async1 end needs to wait 3 microticks to execute

Async1 its equivalent before v8 optimization of the old version of the code is

function async1(){
  new Promise((resolve) = >{
    const p = new Promise(res= >res(async2()))
    p.then(() = >{
      console.log('async1 end')
      resolve()
    })
  })
}
Copy the code

The result of the new browser is

// script start --> async2 end --> Promise --> script end --> async1 end 
// promise1 --> promise2 --> setTimeout
Copy the code

In this case, the above P2 package P1 is merged into one, i.e., await after Promise will not be wrapped again

Async1 its equivalent v8 optimized code is

function async1(){
  new Promise((resolve) = >{
    const p = Promise.resolve(async2())
    p.then(() = >{
      console.log('async1 end')
      resolve()
    })
  })
}
Copy the code

summary

  1. In the new browser, await promiseFun, 3 Microticks are optimized for 2 microticks
    • New Promise is replaced by promise.resolve
    • The promise. resolve argument returns the Promise directly if it is a Promise

supplement

Problem tracing can be viewed

  • Faster asynchronous functions and promises
  • How does V8 implement faster await? Have a deep understanding of the operation of await

self-test

Since the test 1

console.log(1);

setTimeout(() = > {
  console.log(2);
  Promise.resolve().then(() = > {
    console.log(3)}); });new Promise((resolve, reject) = > {
  console.log(4)
  resolve(5)
}).then((data) = > {
  console.log(data);
})

setTimeout(() = > {
  console.log(6);
})

console.log(7)
Copy the code
Click to see the answer

The output

  
  // 1 4 7 5 2 3 6
  Copy the code

Self-test 2

console.log(1);

setTimeout(() = > {
  console.log(2);
  Promise.resolve().then(() = > {
    console.log(3)}); });new Promise((resolve, reject) = > {
  console.log(4)
  resolve(5)
}).then((data) = > {
  console.log(data);
  
  Promise.resolve().then(() = > {
    console.log(6)
  }).then(() = > {
    console.log(7)
    
    setTimeout(() = > {
      console.log(8)},0);
  });
})

setTimeout(() = > {
  console.log(9);
})

console.log(10);
Copy the code
Click to see the answer

The output

  
  // 1 4 10 5 6 7 2 3 9 8
  Copy the code

reference

  • Detailed explanation of JavaScript operation mechanism: Talk about Event Loop again
  • MDN: Concurrency model and event loop

The original text is first published in personal blog, the special series will sort out many articles, and we learn together, common progress, if there is a mistake, please correct

Browser series

  • 1. Browser Thematic series – Caching Mechanisms
  • 2. Browser Thematic series – Principles of rendering
  • 3. Browser Thematic series – Block rendering
  • 4. Browser Thematic Series – Local Storage
  • 5. Browser Special Series – Web Security
  • 6. Browser Series – Browser kernel
  • 7. Browser Thematic series – Cross domain and cross site
  • 8. Browser Thematic Series – Event loops