In fact, we use a lot of timers in our daily projects. Before we didn’t know the event loop mechanism, we only knew that there would be delay when using timers, but we didn’t know the specific reason, so I summarized this article to share with you.

Why an event loop?

At first, I thought this concept was something of JS. Later, WHEN I looked at various documents, I could not find any description of this concept. By chance, when I looked at HTML specification documents, I found that this concept was put forward by HTML. The specification’s exact words are to coordinate events, user interactions, scripting, rendering, networking, etc., hence the concept of event loops.

The event loop mechanism tells us the overall order of execution of the JavaScript code we write, summed up as synchronous tasks -> round -> round.

The following figure shows the flow of a simple event loop (stolen for reference only 😏)

  1. One of the great features of JavaScript is that it is single-threaded. Asynchronous operations are placed in an event loop queue, waiting for the main execution stack to execute. There is no special asynchronous execution thread. Of course, web workers in the new standard involve multithreading, and I don’t know much about it, so I won’t discuss it here.

  2. During the execution of JavaScript code, not only the function call stack is used to determine the execution order of functions, but also the task queue is used to determine the execution of other codes.

Queue: First in, first out

  1. Event loops are unique within a thread, but there can be multiple task queues.

  2. Task queues are divided into macro-tasks and micro-tasks.

  3. Macro-task includes: Script, setTimeout, setInterval, setImmediate, I/O, UI rendering.

  4. Micro-tasks include: Process. nextTick, Promise, Object.observe(deprecated), MutationObserver(new html5 feature).

  5. SetTimeout /Promise etc we call task sources. What enters the task queue is the specific execution task they specify. Tasks from different task sources enter different task queues. Where setTimeout and setInterval are homologous.

The callback function in setTimeout is the task that enters the task queue
setTimeout(function() {
  console.log('123')})// setTimeout as a task dispenser, this function executes immediately,
// The task it assigns, its first argument, is deferred execution.
Copy the code
  1. Each of these tasks, whether macro-task or micro-task, is performed with the aid ofFunction call stackTo complete.

Event queue of js execution order

  1. An event queue is a queue of functions (an array of functions) that provides a callback to another process each time it executes a time-consuming operation (handled by another process).
  2. When the other process has completed the time-consuming operation, the callback function, along with the result of the processing, is posted to the function queue as an argument.
  3. Js engine, which takes a function from the queue to execute.
  4. Once the execution is complete, a function is fetched from the queue and executed until the queue is empty.
  5. Then, if someone else posts the event to the queue, the JS engine runs again.

For example, the JS engine is like the boss of a company

The boss doesn’t need to do things himself, he assigns the task to the corresponding staff, and then records the task in the work log. What if one person finishes the task and walks over to the boss, what if the boss is working on something?

Wait for the result of the incident to be handed to the boss’s secretary;

If one more person finishes, continue to the secretary (sort, report).

Here’s a diagram to illustrate the process:

// Its event queue is very complex in JS (V8, not to be covered here)

// Simulate an array
var queue = [
  [], // The timer queue is specifically used to hold timeout callbacks[].// Pending poll queue (not a queue, this is dedicated to IO, lots of queues, but we don't need to worry about it) can simply be thought of as AN IO queue[].// The Check queue is dedicated to storing setImmediate callbacks
  [] // The close queue is used to store close behavior
]

// The js engine processes the queue
var i = 0;
var len = queue.length
// The process inside the engine
while (true) { // Execute until all queues are empty; And if it is in the stopped state, the loop is activated as soon as an event enters the column.
  wihle (i < len) {
    var subqueue = queue[i]
    // Process the subqueue
    i++
  }
  i = 0
}
Copy the code

Let’s start with a simple example

What is the output order of these lines of code?

setTimeout(function() {
  console.log('timer1')},1000)
console.log('main process 1')
console.log('main process 2')
setTimeout(function() {
  console.log('timer2')},0)
console.log('main process 3')
console.log('main process 4')
setTimeout(function() {
  console.log('timer3')},0)
// main process 1 ~ 4
// timer2
// timer3
// Timer1 is output after 1s
Copy the code
  1. The setTimeout execution is synchronous and puts the callback function into the timer queue of the event queue. It will not execute now and records the execution time.
  2. In the console.log series, the code in the event queue is enabled after execution.
  3. After entering the event queue, the timer queue starts to run first, and only one function is found, which is taken out and executed.
  4. Now you have to be careful
  • The function in the timer event queue has a time range (1000ms). If 900ms has passed, it will not execute the function, and it will continue to iterate pending, roll, Check, close… The queue;

  • Pending, roll, Check, close, etc. The timer object is then called back to the pending, roll, check, close, etc. The queue;

  • Once these queues are traversed, the timer object is called back, and if 1000ms (possibly 1002ms) has passed, the timer object is called back.

The timer specifies a time threshold after which the specified callback may be executed, but not at the time expected. Callbacks specified by the timer are scheduled to be executed as soon as the time expires. This is because operating system scheduling and the execution of other callbacks can exacerbate the delay. This is why timers are delayed.

There is a picture and there is a truth

Let’s do an easy one

With the familiar Promise

setTimeout(function() {
  console.log('timeout1');
})

new Promise(function(resolve) {
  console.log('promise1');
  for(var i = 0; i &lt; 1000; i++) {
    i == 99 && resolve();
  }
  console.log('promise2');
}).then(function() {
  console.log('then1');
})
console.log('global1');
Copy the code
  1. SetTimeout is a macro task source, so its role is to distribute the task to the macro task queue.

  2. When you encounter a Promise, the first argument in the Promise constructor is executed on new, so it does not enter any other queue, but executes directly on the current task. Promise1, promise2, Subsequent. Then packets are sent to the micro-task Promise queue.

  3. Moving on, globa1 is output, and the global task is complete.

  4. After the first macro task is executed, all executable microtasks are executed. At this point, there is only one task, then1, in the Promise queue, so it can be executed directly. The result of execution is then1, and its execution, of course, also goes into the function call stack.

  5. When all of the micro-TAST has been executed, the first loop is complete. That’s when the second cycle begins. The second cycle again starts with macro- Task.

  6. At this point, we see that the only macro task waiting to execute is the one with a timeout1 in the setTimeout queue. So simply execute the output timeout1.

  7. At this point, there are no tasks in either the macro task queue or the microtask queue, so the code will not output anything else, and the execution is finished.

Pictured above,

And finally, a slightly more complicated one

Is it too complicated to have any practical significance 😏 just enough to understand

console.log('global1');

setTimeout(function() {
    console.log('timeout1');
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')})})new Promise(function(resolve) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise_then')})console.log('global2');
Copy the code

It seems a little messy, don’t worry, take your time and analyze it step by step:

  1. The macro task script executes first. Global push. Global1 output.

  2. SetTimeout is encountered during execution. SetTimeout acts as a task dispatcher to distribute tasks to the corresponding macro task queue.

  3. The implementation meets the Promise. Promise’s then method distributes the task to the corresponding microtask queue, but the methods in its constructor execute directly. Therefore, the promise gets the second output. The. Then is sent to the microtask queue.

  4. Then output global2, where there is only one task in the microtask queue, and execute the promise_THEN output. The cycle ends when all executable microtasks are completed.

  5. The next loop continues from the macro task queue, with setTimeout in the macro task queue, outputs timeout1, and then a Promise outputs timeout1_PROMISE, timeout1_THEN is put into the microtask queue, Execute the microtask to output timeout1_THEN, and the entire execution ends there.

Continue to above

It is important to note that the order of execution, or the priority of execution, varies from scenario to scenario, including different versions of Node, browser to browser, etc.

Ok, this is the end of the article, it may be a little boring, where the summary is not in place, please contact me for modification.