Writing in the front

Both browser-side and server-side Node.js use EventLoop, a feature of single-threaded, non-blocking IO based on Javascript. There are macro and micro task queues in the EventLoop event queue. Analyzing the operation mechanism of macro and micro tasks helps us understand the code execution logic in the browser.

So, we have to ask a few questions:

  • What role does the browser EventLoop play?
  • What role does EventLoop play on node.js server?
  • What are the methods for macro and micro tasks?
  • Macro tasks and microtasks are nested with each other. What is the order of execution?
  • What is the execution order of Process.nextick and other microtask methods in Node.js together?
  • Vue also has a Nextick, what is its logic?

EventLoop for the browser

EventLoop is an important knowledge point for asynchronous programming of Javascript engine, and also a key point for learning the underlying principle of JS. We know that JS performs all operations on a single thread, which, although single threaded, is always efficient at solving problems and gives us the illusion of “multi-threading”. This is achieved through some efficient and reasonable data structures.

Call Stack

Call stack: Is responsible for keeping track of all the code to be executed. Whenever the function in the call stack completes, it is popped off the stack and pushed if there is code that needs input.

Event Queue

Event queue: Responsible for sending new functions to the queue for processing. Event execution queue conforms to the queue in the data structure, first in first out characteristics, when the first to enter the event is first executed, the completion of the first to eject.

Each time an asynchronous function in the Event Queue is called, it is sent to the browser API. Based on the command received by the call stack, the API begins its own single-threaded operation.

For example, when an event executes a queue operation setTimeout event, it is now sent to the browser’s corresponding API, which waits until an agreed time to send it back to the call stack for processing. That is, it sends operations to an event queue, which forms a loop for asynchronous operations in Javascript.

The Javascript language itself is single-threaded, while the browser API acts as a separate thread, facilitated by an event loop that constantly checks to see if the code on the call stack is empty. If empty, it is added to the call stack from the event execution queue; If not empty, the code in the current call stack takes precedence.In EventLoop, each loop is called a tick. The main order is:

  1. The execution stack selects the first macro task to be queued and executes its synchronization code until completion
  2. Checks if there are any microtasks, and if there are, the execution knows that the microtask queue is empty
  3. If you’re on the browser side, you’ll basically render the page
  4. Start the next cycle of the tick, executing some asynchronous code in the macro task, such as setTimeout

Note: The macro task that calls the stack first usually returns the result of execution last.

In fact, EventLoop uses two internal queues to implement the asynchronous tasks put in by the Event Queue. The tasks represented by setTimeout are called macro tasks and placed in the Macrotask Queue. The tasks represented by promises are called microtasks and are placed in the Microtask Queue.

The main macro and micro tasks are:

  • Macrotask Queue:
    • Script overall code
    • SetTimeout and setInterval
    • setimmediate
    • I/O (Network request complete, file read/write complete event)
    • UI rendering (parsing DOM, calculating layout, drawing)
    • EventListner Listens for events (mouse clicks, scrolling, zooming in and out, etc.)
  • Microtask Queue:
    • process.nextTick
    • Promise
    • Object.observe
    • MutationObserver

Macro task

Message queues and event loops are introduced into the page process, and we call these tasks in message queues macro tasks. JS code can not accurately control the position of the task to be added to the queue, can not control the position of the task in the message queue, so it is difficult to control the time to start executing the task. Such as:

function func2(){
	console.log(2);
}
function func(){
	console.log(1);
  setTimeout(func2,0);
}

setTimeout(func,0);
Copy the code

Do you think the code above prints 1 and 2 all at once? It doesn’t. Because in the JS event loop mechanism, when setTimeout is executed, the event will be suspended and some other system tasks will be executed only after other execution is completed, so the execution time interval is not controllable.

Micro tasks

A microtask is a function that needs to be executed asynchronously, after the main function completes but before the current macro task ends. When JS executes a script, v8 creates a global execution context for it, and v8 creates an internal microtask queue to store the microtasks.

So how do microtasks come about?

  • A MutationObserver is used to monitor a DOM node, or to add or remove some molecular nodes to the node. When the DOM node changes, a microtask is generated to record the DOM changes.
  • With promises, microtasks are also generated when promise.resolve () or promise.reject () is called.

Microtasks generated by DOM node changes or by using promises will be sequentially stored in the microtask queue by the JS engine.

MutationObserver is a set of methods used to listen for DOM changes. Although DOM listening was required frequently, early pages did not provide support for listening, and the only way to do this was polling. If the time interval is too long, DOM changes do not respond timely. If the interval is too short, a lot of useless work is wasted checking the DOM. Starting with DOM4, the W3C introduced a MutationObserver that can be used to monitor DOM changes, including property changes, node additions, content changes, etc. Each time a DOM node changes, the rendering engine encapsulates the change record into a microtask and adds the microtask to the current microtask queue.

MutationObserver adopts the strategy of “asynchronous + micro-task”, which solves the performance problem of synchronous operation through asynchronous operation and real-time problem through micro-task.

When the JS engine is ready to exit the global execution context and clear the call stack, the JS engine will check the microtask queue in the global execution context and then execute the microtasks in the queue in order. New microtasks created during the execution of a microtask are not deferred to the next loop, but continue to be executed in the current loop.

Microtasks and macro tasks are bound, and each macro task creates its own microtask queue when it executes. The duration of the microtask affects the duration of the current macro task. In a macro task, create a macro task and a microtask for callbacks, and in any case, the microtask precedes the macro task.

Browser EventLoop works by:

  • The JS engine first retrieves the first task from the macro task queue
  • After the execution, all the tasks in the micro-task are taken out and executed in sequence. If new microtasks are created during this process, they need to be executed in sequence
  • And then take out the next one from the macro task queue. After the execution is completed, take out all the micro tasks in the macro task event from the micro task queue and execute them in turn. Repeat until all the events in the macro task and micro task queue are completed

Note: An EventLoop loop processes one macro task and all the microtasks generated in the loop.

Node. Js EventLoop

Node.js is defined as: When Node.js starts, it initialises the event loop, processes the supplied input script (or dumps it into the REPL, which is not covered in this article), and it may call some asynchronous API, schedule the timer, or call process.Nexttick () to start processing the event loop.

Above is the EventLoop flow chart of Node.js.

  • Timers: setTimeout and setInterval are executed
  • I/O callback phase: Execute system-level callback functions, such as TCP failed callback functions
  • Idle and Prepare: Indicates the Idle and preparation phases of a Node
  • Poll phase: Retrieves new I/O events; Perform I/ O-related callbacks (in almost all cases, except for closed callback functions, those scheduled by timers and setImmediate()), where Node will block at the appropriate time.
  • The Check phase: The setImmediate() callback function executes here.
  • Close callback phase: some closed callback functions, such as socket.on(‘ Close ‘,…) .

The browser-side task queue generates only one callback function per event loop and then executes the microtask queue. On the Node.js side, when it is the turn of a macro task queue, all the current tasks in the queue will be executed, but the tasks added to the bottom of the queue will wait for the next poll to execute.

Process.nextTick()

Process. nextTick(callback, optional args);Copy the code

NextTick adds callback to the nextTick Queue, which is FIFO queued after the current Javascript stack execution and before the next EventLoop execution. If a recursive call to process. nextTick could result in an infinite loop, the recursion needs to be terminated at the appropriate time.

Process.nextTick is actually a microtask and part of the asynchronous API, but process. nextTick is not technically part of an EventLoop. If process. nextick is called at any time during a given phase, all callbacks passed into process. nextTick will be executed before the event loop continues, which may result in the event loop never reaching the polling phase.

Why is an API like process. nextTick allowed to exist in Nodejs? Part of the reason is because of the design philosophy that apis in NodeJS are always asynchronous, even where asynchronous is not required.

function apiCall(args,callback){
  if(typeofargs ! = ="string") {return process.nextTick(callback,new TypeError("atgument should be string")); }}Copy the code

As you can see from the code above, you can pass an error to the user, but this can only be executed after the user code has been executed. Using process.nexttick ensures that the callback to apiCall() is always executed after the user code is executed and before the event loop resumes.

So what does nextTick do in Vue?

Vue performs DOM updates asynchronously, and when data changes, VUE opens a queue to buffer all data changes that occur in the same event loop. If the same Watcher is triggered more than once, it will only be pushed into the queue once. This removal of duplicate data while buffering is important to avoid unnecessary computation and DOM manipulation. And then in the next event loop tick. For example, when you set vm.someData = “yichuan”, the component does not immediately perform rerendering. When the refresh queue is, the component updates the next “tick” when the event loop queue is empty.

  • Process. nextTick is executed in the following order: If there are more than one Process. nextTick before each EventLoop is executed, the execution time of the next time loop will be affected
  • Vue: Each data update in the Nextick method will be applied to the next view update

The impact of EventLoop on rendering

The requestIdlecallback and requestAnimationFrame methods are not native to JS, but are provided by the browser host environment. As a complex application, browser is multithreaded. JS threads can read and modify DOM, and render threads also need to read DOM. This is a typical problem of multithreading competing for resources. So the browser makes the two threads mutually exclusive, meaning that only one thread can run at a time.

The JS thread and the render thread are mutually exclusive, but requestAnimationFrame creates a link between the two incompatible threads by linking EventLoop to render. By calling requestAnimationFrame(), we can execute the callback before the browser renders the next time. When will the next render be? What does rendering have to do with EventLoop?

To put it simply, before the end of each EventLoop, render again to determine whether there is currently a rendering opportunity, which is screen limited. The browser refresh frame rate is 60Hz, that is, 60 times within 1s. The browser’s rendering time doesn’t have to be less than 16.6ms, because the screen won’t be displayed after rendering,

Of course, browsers are not guaranteed to render every 16.6ms. In addition, browser rendering is influenced by processor performance and JS execution efficiency.

RequestAnimationFrame is guaranteed to be called before the browser renders next time, and in fact can be thought of as an advanced setInterval timer. They both execute callbacks at regular intervals, except that requestAnimationFrame is adjusted by the browser over time, whereas setInterval is user-specified. Therefore, requestAnimationFrame is more suitable for making changes to each frame of the animation.

RequestAnimationFrame is not a macro task in EventLoop, or it is not part of the EventLoop lifecycle, but a new hook developed by the browser that occurs before rendering. In this case, our understanding of the microtask needs to be updated. It is also possible that the microtask will be executed after the requestAnimationFrame callback is completed. Therefore, microtasks are not processed after each EventLoop as described earlier, but after the JS function call stack is emptied.

When there are no tasks to be processed in an EventLoop, the browser may be idle. This idle time can be used by requestIdlecallback for low-priority tasks that do not need to be executed immediately, as shown in the figure below:Also, to prevent the browser from being busy all the time and never being able to execute the requestIdlecallback callback, the browser provides an extra setTimeout function that sets a deadline for the task and allows the browser to plan the execution of the task according to that deadline.

Refer to the article

  • The Core Principles of Javascript
  • Node.js
  • Javascript Advanced Programming

Write in the last

This article discusses the differences between EventLoop in browsers and Node.js. EventLoop itself is not a complex concept, but we need to understand the similarities and differences between them based on the platform on which JS is run.