Author: Horace blog: recently simple set up a blog ~ PS: new xiao White one, if there are mistakes, welcome to point out ~

The author has been busy with the project recently, and the output of the article has been left behind for a period of time. This time, we will talk about an important knowledge point in an interview — Event Loop

For those of you wondering what else an EventLoop can write, let me take you through this article to get you through the EventLoop and its related knowledge.

What is an Event Loop

Before we start talking about Event Loop, let’s take a look at what it really is.

In computer science, the event loop is a programming construct or design pattern that waits for and dispatches events or messages in a program. The event loop works by making a request to some internal or external “event provider” (that generally blocks the request until an event has arrived), then calls the relevant event handler (“dispatches the event”). The event loop is also sometimes referred to as the message dispatcher, message loop, message pump, or run loop.

An Event Loop is a program structure. My personal understanding is that Event Loop in JS is a mechanism for browsers or Nodes to coordinate JavaScript’s single-threaded execution without blocking.

Why learn Event Loop?

One might wonder why the front end is learning the low-level Event Loop, and not just because it’s a common interview question.

  1. As a programmer, it is important to understand how the program works to help you output better code.
  2. The front end is a very broad field, and the technology is constantly being updated and iterated, and the underlying principles are mastered to deal with the new technology.
  3. A good programmer needs to be able to make the code work the way he wants it to. If he can’t understand how the code works, there’s no need to control his code.

Processes and threads

As I mentioned earlier, Event Loop is a solution to the single-thread blocking problem, so let’s start by talking about processes and threads. It is well known that JavaScript is a single-threaded language, so let’s take a look at the definitions of processes and threads:

define

  1. Process: A process is the smallest unit of CPU resource allocation
  2. Thread: Thread is the smallest unit of CPU scheduling

To be honest, you don’t get a sense of what processes and threads are by definition. In simple terms, the process of simple understanding is our ordinary use of the program, such as QQ, browser, disk and so on. Processes have their own independent memory space address and have one or more threads, and threads are a further division of process granularity.

In more general terms, a process is like a factory, with multiple factories that exist independently of each other. Threads are like workers in a factory, sharing resources and accomplishing the same big goal.

JS single thread

As most people know, JavaScript is a dynamic, interpreted language that is cross-platform. When asked why JavaScript is a single-threaded language, someone might say, “JavaScript is a single-threaded language because of the nature of the language. JavaScript is a single-threaded language by nature.”

JavaScript has been single-threaded since its inception, presumably because it didn’t want to make the browser too complex, since multiple threads share resources and potentially modify each other’s results, which would be too complex for a web scripting language.

To be precise, I think single-threaded JavaScript means that the JavaScript engine is single-threaded. JavaScript engines do not run independently, and cross-platform means that JavaScript is dependent on the host environment in which it is run – the browser (in most cases, the browser).

The browser needs to render the DOM. JavaScript can modify the DOM structure. When JavaScript executes, the browser DOM rendering stops. If the JavaScript engine thread is not single-threaded, multiple JavaScript segments can be executed at the same time, and if they all manipulate the DOM, DOM collisions can occur.

For example, executing two scripts on the same DOM element at the same time, one modifying the DOM and the other deleting the DOM, makes the browser confused, and it doesn’t know which one to listen to. There is competition for resources, which is one of the reasons JavaScript is single threaded.

3. Browser

Multithreading in browsers

As mentioned earlier, the hosting environment in which JavaScript is run, the browser, is multithreaded.

In Chrome’s case, we can check it out in Chrome’s Task manager.

When you open a Tab page, you create a process. If you open a page from one page to another that belongs to the same site as the current page, that page will reuse the parent page’s rendering process.

The browser main thread is the resident thread

  1. GUI rendering thread
    • Draw pages, parse HTML, CSS, build DOM trees, layout and draw, etc
    • Page redraw and reflow
    • Mutually exclusive with the JS engine thread, so called JS executes blocking page updates
  2. JS engine thread
    • Responsible for the execution of JS script code
    • Responsible for the quasi-execution of events that are ready to be executed, that is, events that the timer count ends, or that are returned correctly after the asynchronous request succeeds
    • It is mutually exclusive with the GUI rendering thread, and taking too long to execute will block the rendering of the page
  3. Event trigger thread
    • Responsible for handing the prepared event to the JS engine thread for execution
    • Multiple events queued to a task (single thread of JS)
  4. Timer trigger thread
    • Responsible for executing asynchronous timer class events, such as setTimeout and setInterval
    • Add the registered callback to the end of the queue when the timer expires
  5. HTTP request thread
    • Responsible for executing asynchronous requests
    • When the main thread execution code encounters an asynchronous request, it will hand the function to the thread to handle. When listening for a state change event, the thread will add the callback function to the end of the task queue to wait for execution

It doesn’t matter if I don’t understand it, I’ll talk about it later.

4. Event Loop on browser

The Event Loop on the browser side looks like this.

The figure above is a diagram of the running mechanism of JS. The running time of JS can be roughly divided into several parts:

  1. Call Stack: All synchronization tasks are executed on the main thread, forming an execution Stack. Due to the single JS thread, only one task can be executed in the Call Stack at a time. After the synchronization tasks are executed, the task queue provides tasks to the Call Stack for execution.
  2. Task Queue: A Task Queue that stores asynchronous tasks. When asynchronous tasks are ready to be executed, the Task Queue notifies the main thread and the Task is executed on the main thread. The task queue is filled with asynchronous operations that have already been completed, rather than registering an asynchronous task to be placed in the task queue.

At this point, Event Loop can also be understood as a process of continually fetching tasks from the task queue for execution.

Synchronous and asynchronous tasks

As mentioned above, JavaScript is a single-threaded language and can only execute one task at a time. If all the tasks are synchronous tasks, the program may be in suspended animation due to waiting, which is very unfriendly for a language with strong user experience.

For example, when you request resources from the server, you can’t keep repeating the loop to see if you get the data. It’s like when you order takeout and then start calling to see if it’s delivered, the takeout guy will beat you with a spatula. So, in JavaScript, there are synchronous tasks and asynchronous tasks, and the asynchronous task registers the callback function and notifies the main program when the data arrives.

concept

This section briefly introduces the concepts of synchronous and asynchronous tasks.

  1. Synchronous tasks: You have to wait until the results come in before you can do anything else. For example, when you boil water, you wait by the kettle and do nothing else.
  2. Asynchronous tasks: You don’t need to wait for the results to come in before moving on. You can do other things while the results come in and be notified of the results. For example, when you boil water, you can do what you want to do, and then you can do it when you hear the water boiling.

From a conceptual point of view, asynchronous tasks are somewhat more efficient than synchronous tasks, and the core is improved user experience.

Event Loop

Event Loop can schedule tasks well, macro tasks and micro tasks are also known, now let’s take a look at its scheduling mechanism.

When executing the JavaScript code, the main thread will execute the code step by step from top to bottom. Synchronous tasks will be added to the execution stack and executed first, and asynchronous tasks will put the registered callback function into the task queue when they get the result. When there is no task executing in the execution stack, The engine reads tasks from the task queue and pushes them onto the Call Stack to process execution.

Macro and micro tasks

Now there is a problem. The task queue is a message queue, first-in, first-out, that is, subsequent events are added to the end of the queue until the previous events have finished. If important data needs to be retrieved or an event needs to be processed during execution, it cannot be processed in a timely manner according to the first-in, first-out order of the queue. This gives rise to macro tasks and microtasks, which allow asynchronous tasks to be processed in a timely manner.

Ever see a case in point is very good, macro and micro tasks for image is: you go to business hall to run a business, there will be a line number when call the number to you when you go to window do prepaid phone business tasks performed (macro), when you deal with top-up and you want to change a package (task), staff will directly help you to do at this time, can’t get you to the end of the line.

Therefore, asynchronous tasks mentioned above can be divided into macro tasks and micro tasks. JS runtime task queues can be divided into macro task queues and micro task queues, corresponding to macro tasks and micro tasks respectively.

Let’s start with macro and micro tasks:

  • Macro task:
    1. Script (whole code)
    2. setTimeout
    3. setInterval
    4. I/O operations
    5. UI rendering (take this author with a grain of salt)
  • Micro tasks:
    1. Promise.then
    2. MutationObserver

Sequence of events

  1. To execute the synchronization task, the synchronization task does not need to do special processing, directly execute (in the following steps encountered synchronization task is the same processing) – the first round starts from script
  2. Retrieves the queue head task from the macro task queue for execution
  3. If a macro task is generated, it is put into the macro task queue and executed in the next round
  4. If a microtask is generated, the microtask is placed in the microtask queue
  5. After executing the current macro task, take out all tasks in the microtask queue and execute them in turn
  6. If a new microtask is created during the execution of the microtask, the execution of the microtask continues until the queue of the microtask is empty
  7. Cycle, cycle above 2-6

In summary: Synchronous task/macro task -> Execute all microtasks generated (including microtasks generated) -> Synchronous task/macro task -> Execute all microtasks generated (including microtasks generated) -> loop……

Note: microtask queues

Take a chestnut

Now let’s take a look at an example:

The reason why I put it up here is so that you can walk through it in the order you want to run it before you look at the analysis, and then you can look at the analysis after you’ve written the answer. (Synchronous and macro tasks in green, microtasks in red)

+ console.log('script start')
+ setTimeout(function() {
+ console.log('setTimeout')
+}, 0)
+ new Promise((resolve, reject)=>{
+ console.log("promise1")
+ resolve()
+})
- .then(()=>{
- console.log("then11")
+ new Promise((resolve, reject)=>{
+ console.log("promise2")
+ resolve();
+})
- .then(() => {
- console.log("then2-1")
-})
- .then(() => {
- console.log("then2-2")
-})
-})
- .then(()=>{
- console.log("then12")
-})
+ console.log('script end')
Copy the code
  1. Console.log () is first encountered, and outputscript start
  2. The macro task is generated when setTimeout is encountered, and the macro task queue [setTimeout] is registered. The next round of Event Loop will be executed
  3. You then encounter a new Promise construction declaration (synchronization) and log outputpromise1And then resolve
  4. Resolve matches the first THEN of promise1 and registers that THEN on the microtask queue [then11] to continue the current overall script execution
  5. The last log, outputscript end.Currently perform stack clearing
  6. Retrieve queue head task ‘then11’ from microtask queueExecute, there’s a log, outputthen11
  7. Down you come to the new Promise construction declaration (synchronization), log outputpromise2And then resolve
  8. Resolve to match the first THEN of the promise1, register this THEN with the microtask queue [THEN2-1], and then generate the second THEN of the promise1. Register this THEN to the microtask queue [THEN2-1, THEN12]
  9. Take out the micro task team’s first task ‘then2-1’Execute, log outputthen2-1To triggerThe second then of promise2, registered toMicrotask queue [THEN12, THEN2-2]
  10. Take out the micro task team head task ‘then12’The log output,then12
  11. Take out the micro task team first task ‘then2-2’The log output,then2-2
  12. When the microtask queue completes, don’t forget the setTimeout, log output in the macro task queuesetTimeout

After the above analysis, WE hope that we do not confuse you, and the final output result is: script start -> promise1 -> script end -> then11 -> promise2 -> then2-1 -> then12 -> then2-2 -> setTimeout

Macro task? Micro tasks?

I don’t know if you might be confused after looking at macro tasks and micro tasks, but macro tasks and micro tasks are asynchronous tasks, and micro tasks, as mentioned earlier, are created to solve some necessary events in time.

  • Why microtasks? The reason why there is a micro-task has been mentioned before, and I will not repeat it here. Simply speaking, it is to deal with some tasks in time, otherwise the data obtained when the final execution may be contaminated and cannot reach the expected target.

  • What is a macro task? What are microtasks? I believe that when you learn Event Loop to find information, you will certainly talk about macro task and micro task in various materials, but I wonder if you have asked yourself: what is macro task? What are microtasks? How to distinguish between macro tasks and micro tasks? Can’t just acquiesce to accept the concept, here, I will say according to my personal understanding (Hu) Ming (che)

  • In fact, there is no macro task and micro task code or description in Chrome source code, in JS conference mentioned micro task this term, but also did not say what is micro task.

    As mentioned at the beginning of the macro Tasks article, there is a process for every page in Chrome. The process has multiple threads, such as JS thread, rendering thread, IO thread, network thread, timer thread, etc. The communication between these threads is realized by adding a task (postTask) to the task queue of the object. The essence of a macro task can be thought of as a multithreaded event loop or message loop, which is a message queue for communication between threads.

    Take setTimeout, for example. When it encounters it, the browser will say to the Event Loop, “Hey, I have a task for you,” and the Event Loop will say, “OKAY, I’ll add it to my todoList, and then I’ll execute it, which requires an API call.”

    Macro tasks are distributed by the browser, irrelevant to the JS engine, and participate in the task of Event Loop scheduling

    Micro task micro task is generated when running macro task/synchronization task, is the current task, so it does not need browser support, built-in JS, do not need API support, directly in the JS engine is executed.

Special point

  1. Async implicitly returns a Promise as a result

  2. Immediately jump out of async function after execution of await and surrender execution ownership

  3. Get the right to execute again after the rest of the current task has been executed

  4. The Resolve Now Promise object is executed at the end of this cycle, not at the beginning of the next cycle

Here’s another chestnut

  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

Following the previous analysis method, a result will be obtained: script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout

As you can see async1 gets the execution right as the end of the queue of microtasks, but after Chrome73(canary) async execution is optimized to execute before the output of promise1 and promise2. PromiseResolve optimizes await with PromiseResolve to reduce Promise creation.

5. Event Loop in Node

Node also has macro and micro tasks, similar to event loops in browsers. Node differs from the browser event loop in that it has multiple macro task queues, whereas the browser has only one macro task queue.

Node architecture is based on Libuv, which is one of the power sources of Node itself. Some low-level operations can be called through it. The Event Loop function in Node is encapsulated in Libuv.

Macro and micro tasks

Macrotasks and microtasks in Node have been added to the browser-side JS, and only those that are not are listed here:

  • Macro task
    1. setImmediate
  • Micro tasks
    1. process.nextTick

The six stages of the event cycle

Six stages

The Node event loop is divided into six phases, each corresponding to a macro task queue, which is equivalent to a classification of macro tasks.

  1. Timers Perform setTimeout and setInterval callbacks
  2. I/O Callbacks handle network, stream, and TCP error callbacks
  3. Idel, prepare — Internal use of node in idle phase
  4. Poll (round) Executes the I/O queue in a poll to check whether the timer is running out of time
  5. Check stores the setImmediate callback
  6. Close Callbacks Close callbacks, such as sockect.on(‘close’)

Round robin order

Round order of execution – each phase waits for the corresponding macro task queue to complete before entering the macro task queue of the next phase

  1. timers
  2. I/O callbacks
  3. poll
  4. setImmediate
  5. close events

Microtask queues are executed every two phases

The Event Loop process

  1. Execute globally synchronized script code
  2. To execute the microtask queue, first execute all tasks in the Next Tick queue and then execute all tasks in the other microtask queues
  3. Start executing macro tasks in six stages, starting from the first stage to execute all tasks in your macro task queue.
  4. After the macro tasks of each phase are completed, the micro tasks are performed
  5. TimersQueue -> Step 2 -> I/O Queue -> Step 2 -> Check Queue -> Step 2 -> Close Callback Queue -> Step 2 -> TimersQueue…

Note that the nextTick event is a separate queue and takes precedence over the microtask, so all tasks in the nextTick queue are executed before all tasks in the microtask queue are executed after the current macro/synchronization task is completed.

SetTimeout and setImmediate

I’m going to talk a little bit about setTimeout and setImmediate, but setTimeout timers are familiar, so setImmediate

The setImmediate() method is used to place some long-running actions in a callback function and run the callback function immediately after the browser completes other actions, such as events and display updates. This is by definition to prevent long operations from blocking later operations, which is why the check phase runs after the sequential comparison.

Take a chestnut

Let’s look at an example like this:

setTimeout((a)= > {
  console.log('setTimeout')},0)

setImmediate((a)= > {
  console.log('setImmediate')})Copy the code

The timers and check phases are involved here. According to the above running order, the timers phase is executed first and before the Check phase. Run this program and you will see the following result:

But run it a few more times and you’ll see something like this:

SetImmediate Mediate (❓) The setImmediate mediate output does not lead to setTimeout

Analysis of the

Let’s analyze the reason. In the timers stage, it is true that the timers stage is before the check stage, but in the timers stage, does the setTimeout here really reach the execution time?

SetTiemout (fn, 0) does not mean to execute without delay, but to execute its callback as soon as a setTimeout can be executed, i.e., as soon as the current event is processed.

SetTimeout (fn, 0) === setTimeout(fn, 1) setTimeout(fn, 1)

The setTimeout callback is performed in the Timers phase, and setImmediate callback is performed in the Check phase. The Event Loop starts by checking the Timers phase, However, it takes time to reach the timers phase before the code starts running, so two situations occur:

  1. If the time for preparing for timers exceeds 1ms and loop -> Timers >= 1 is met, the callback function of the Timers phase (setTimeout) is executed

  2. The timers setup time is less than 1ms and setTimeout does not reach the preset time. Then the check state (setImmediate) callback function is performed. The next time the Event Loop enters the Timers phase, the callback function of the Timer phase (setTimeout) is executed

Using setTimeout and setImediate is a good way to get your code to run in the desired sequence.

  • SetTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout: setTimeout
  const start = Date.now()
  while (Date.now() - start < 10)
  setTimeout((a)= > {
  console.log('setTimeout')},0)

  setImmediate((a)= > {
    console.log('setImmediate')})Copy the code
  • SetImmediate setImmediate is used in the Check phase, rather than after the Timers phase for setTimeout. It is simply a matter of controlling the program’s running environment after the Timers phase.

    Start your application at least from the I/O Callbacks phase – a layer of file reads and writes can control your application in the I/O Callbacks phase 👇

const fs = require('fs')

fs.readFile(__dirname, () => {
  setTimeout((a)= > {
    console.log('setTimeout')},0)
  
  setImmediate((a)= > {
    console.log('setImmediate')})})Copy the code

Changes to Node 11.x

The execution of the Timers phase changes

setTimeout((a)= > console.log('timeout1'))
setTimeout((a)= > {
 console.log('timeout2')
 Promise.resolve().then((a)= > console.log('promise resolve'))})Copy the code
  1. Node 10 and earlier: Consider whether the next timer will join the queue when the last timer has finished. If not, execute other code first. Such as: Timer1 -> Timer2 -> Promise resolve timer1 -> Timer2 -> Timer2 -> Timer2 Timer1 -> Promise resolve -> Timer2

  2. Node 11 and later: Timeout1 -> Timeout2 -> Promise resolve Executes the microtask queue as soon as a macro task in a phase is executed, consistent with browser-side execution.

summary

What’s the difference between Node and the browser side

  1. The Event Loop in the browser is different from the Event Loop in Node.js, and the implementation mechanism is also different
  2. Node.js can be thought of as having four macro task queues and two microtask queues, but there are six phases in macro task execution
  3. Node.js restricted global script code, after the synchronization code is executed, first remove all tasks from the microtask Queue Next Tick Queue and put them into the call stack for execution, then remove all tasks from other microtask queues and put them into the call stack for execution, and then start the 6 stages of macro task. Each phase executes all tasks in its macro task queue (the browser only executes the first task). After the execution of each macro task phase, the micro task is executed, and then the macro task of the next phase is executed, thus forming an event cycle
  4. Macro tasks include….
  5. Microtasks include….

You should be familiar with Event loops on both the browser and Node sides, so here’s a question.

Not directly put the code is to let you think about it and then run it again

One last mouthful

This is the end of this article, or the same words, to be a programmer to know how to know why. I write some articles also want to export the knowledge, to check whether I really understand. The article may still have some unclear or wrong places, welcome to point out directly ~

The resources

Nodejs convention 1 – Queues and Schedules

I have recorded all my learning records in my Github and will continue to update, interested partners can see ~ Github