preface

The last time, I have learned

[THE LAST TIME] has always been a series THAT I wanted to write, aiming to build on and revisit THE front end.

But also to their own shortcomings and technology sharing.

Welcome more comments, comments and jokes.

A series of articles were published on the public account “Full Stack Front-end Selection”. See Nealyang/personalBlog for a collection of my articles. All catalogues are tentative

Execute & Run

First of all, we need to declare that JavaScript execution and execution are two different concepts. Execution generally depends on the environment, such as Node, browser, Ringo, etc., and the execution mechanism of JavaScript in different environments may be different. The Event Loop that we’re going to talk about today is an implementation of JavaScript. So we’ll tease out how Node is executed. Running, on the other hand, is the JavaScript parsing engine. It’s uniform.

About JavaScript

In this article, we only need to keep one sentence in mind under this subtitle: JavaScript is a single-threaded language, both web-worker in HTML5 and cluster in node are “paper tigers”, and cluster is related to process management. The distinction is drawn between processes and threads.

Since JavaScript is a single-threaded language, the problem is that all the code has to be executed sentence by sentence. It’s like standing in line at the canteen and having to order and check out one by one. Those who don’t get in line have to wait

Concept of carding

Before going into the details of the execution mechanism, let’s take a look at some of the basic concepts of JavaScript so that we have an idea and a general outline when we get there.

Event Loop

What is an Event Loop?

In fact, the concept is somewhat vague, because it has to be explained in conjunction with the operation mechanism.

JavaScript has one main thread, the main thread, and a call-stack, also known as the execution stack. All tasks are placed on the call stack and wait for the main thread to execute them.

For the moment, let’s first understand that the big circle in the figure above is an Event Loop. In other words, the JavaScript Event Loop continues throughout the source code file’s life cycle. As long as JavaScript is currently running, the internal Event Loop continues. Find tasks in the queue that can be executed.

Task Queue

Each statement is a task. Each statement is a task

console.log(1);
console.log(2);
Copy the code

In the preceding statement, there are two tasks.

And a queue is a FIFO queue!

So a Task Queue is a Queue of tasks. The Event Loop in JavaScript is constantly coming up to this queue and asking if any tasks are available to run.

Synchronous task, AsyncTask

A synchronous task is code that executes immediately when the main thread is running it, for example:

console.log('this is THE LAST TIME');
console.log('Nealyang');
Copy the code

As soon as the code executes to the console, it prints the result on the console.

An asynchronous task is when the main thread executes the task and says, “Oops! You wait, I don’t carry out now, I finished XXX after I will wait for you to carry out “notice above I said is waiting for you to carry out.

In other words, an asynchronous task is a task that you want to execute first and then put a task in the task Queue to wait for execution

setTimeout(()=>{
  console.log(2)
});
console.log(1);
Copy the code

SetTimeout is an asynchronous task, so the main thread registers an asynchronous callback, and then executes the following statement: console.log(1). Log (2). The specific implementation mechanism will be dissected later.

  • The main thread executes all code from top to bottom
  • Synchronous tasks are executed directly into the main thread, while asynchronous tasks are executed directly into the main threadEvent TableAnd register the corresponding callback function
  • After the asynchronous task is complete,Event TableI’m going to move this function inEvent Queue
  • When the main thread task is finished, theEvent QueueTo read the task, enter the main thread to execute.
  • Cycle as

The above actions continue in a Loop, which is called an Event Loop.

A profound

ajax({
    url:www.Nealyang.com,
    data:prams,
    success:() => {
        console.log('Request successful! ');
    },
    error:()=>{
        console.log('Request failed ~');
    }
})
console.log('This is a synchronization task');
Copy the code
  • The Ajax request comes in firstEvent Table, respectively registeredonErrorandonSuccessCallback function.
  • The main thread performs a synchronization task:Console. log(' This is a synchronization task ');
  • Main thread task completed. LookEvent QueueWhether the task is waiting to be executed, here is constantly checked as long as the main threadtask queueThere are no more tasks to execute, and the main thread is just waiting here
  • When ajax completes, the function is called backpushEvent Queue. (Steps 3 and 4 are in no order)
  • The main thread “finally” waitedEvent QueueAre there intaskYes, execute the corresponding callback task.
  • And so on.

MacroTask, MicroTask

The tasks of JavaScript are not only divided into synchronous tasks and asynchronous tasks, but also divided into macrotasks and microtasks from another dimension.

All synchronous tasks are called MacroTasks. SetTimeout, setInterval, I/O, UI Rendering, and so on are all macrotasks.

Microtasks, why do I emphasize that all synchronous tasks are macrotasks, because we only need to remember a few microtasks, elimination method! Everything else is MacroTask. Microtasks include: Process.nextTick, Promise.then Catch Finally (note I’m not saying Promise), MutationObserver.

Event Loop in the browser environment

Once we’ve sorted out which are microtasks, which are macroTasks except those, which are synchronous tasks and which are asynchronous tasks, it’s time to take a thorough look at JavaScript execution.

As mentioned earlier, execution and execution are different, and execution is context-sensitive. Therefore, we divide the introduction of Event Loop into browser and Node environments.

First put map town building! If you already understand what this diagram means, you can read the Event Loop section of the Node environment directly.

SetTimeout and setInterval

setTimeout

SetTimeout is how long to wait for the callback to execute. SetInterval is how often the callback is executed.

let startTime = new Date().getTime();

setTimeout(()=>{ console.log(new Date().getTime()-startTime); }, 1000);Copy the code

As the above code implies, wait 1s before executing the console. Put it in the browser and execute, OK, that’s what you want.

But this time we’re talking about JavaScript execution, so here’s the code:

let startTime = new Date().getTime();

console.log({startTime})

setTimeout(()=>{console.log(' Start callback time difference:${new Date().getTime()-startTime}`); }, 1000);for(leti = 0; i<40000; i++){ console.log(1) }Copy the code

As shown above, the setTimeout callback is not executed until 4.7s later! In this case, we delete the 1s delay of setTimeout:

let startTime = new Date().getTime();

console.log({startTime})

setTimeout(()=>{console.log(' Start callback time difference:${new Date().getTime()-startTime}`); }, 0);for(leti = 0; i<40000; i++){ console.log(1) }Copy the code

The result is still 4.7s before the setTimeout callback is executed. It looks like the setTimeout delay doesn’t have any effect!

This brings us back to the JavaScript execution flowchart above.

SetTimeout here is simple asynchronous, we analyze the above code step by step through the figure

  • First of all,JavaScriptExecuting code from the top down
  • Encountered encountered the assignment statement, and the firstconsole.log({startTime})Separately as onetask, pushed into the immediate execution stack to be executed.
  • encountersetTImeoutIs an asynchronous task, then register the corresponding callback function. (asynchronous function tells you, js you don’t worry, wait 1s before I call the callback function:console.log(xxx)In theTask QueueC)
  • OK, the JavaScript then goes down and hits 40,000 for loop tasks, which are still running for 1s. In fact, this time the above correction is already inTask QueueIn the now.
  • Wait until all tasks in the immediate execution stack have finished and then look backTask QueueThe asynchronous callback task is already inside the callback task, so proceed.

For example

This is not just about timeouts, but about any asynchrony, such as a network request.

Like, I get off work at 6:00, so I can plan my own activities!

Then pack up the computer (synchronous task), pack up the schoolbag (synchronous task), call the girlfriend to say to have a meal (is necessarily asynchronous task), and then the girlfriend said you wait, I first make up, and so ON I draw good call you.

I can’t just wait and do something else, like LET me know if I’m fixing a bug. An hour later she said, “I’ve done my makeup. Let’s go out to dinner.” No way! I haven’t solved the bug yet. You wait… It doesn’t matter if you spend an hour or five minutes doing makeup… Because my brother is busy at the moment

If I have a bug fixed in half an hour and no more tasks to perform, then wait here! Must wait! Always ready! . Then my girlfriend calls, I finish my makeup, let’s go out for dinner, then it happens, we finish your request or I’m free after timeout, then I have to do it immediately.

setInterval

SetTimeout, of course, can’t miss its twin: setInterval. For execution order, setInterval will put the registered functions into the Task Queue at specified intervals, or wait if the previous tasks take too long.

The point here is that for setInterval(fn,ms), we specify that the fn will be executed every xx ms, which is actually no xx ms, and a FN will enter the Task Queue. Once setInterval’s fn callback takes longer than xx ms, the interval is completely invisible. Look at it carefully. Is that what it is?

Promise

THE use of promises is only covered here, and will be covered in more detail later in THE article “THE LAST TIME To Thoroughly Understand JavaScript async.” We will only talk about the execution mechanism of JavaScript.

As mentioned above, promise.then, catch, and finally are microTasks. The main distinction here is asynchronous. Before we expand the explanation, let’s take a look at the “twist” of the above.

To avoid a bit of confusion for beginners at this point, let’s forget about JavaScript asynchronous tasks for the moment! Let’s call it a synchronization task later.

Given the above constraints, we can say that JavaScript executes every Task from the very beginning from the top down, encountering only tasks that need to be executed immediately and tasks that need to be executed later. Tasks that will be executed later will not be executed immediately when they can be executed, you have to wait for JS to complete the trip

Here’s another analogy

Just like doing the bus, the bus waits for no one, the bus route will stop someone (rural bus! But wait for the bus to come, you say with the driver, I have a stomachache to pull X ~ this time the bus will not wait for you. You can only pull after waiting for the next bus to come (mountain! One bus per route).

OK! You’re done… Wait for the bus, the bus will arrive soon! However, you can’t get on the bus right away because there’s a pregnant woman in front of you! There’s an old man! And the kids, you have to let them get on first, then you can get on!

Pregnant women, old people, and children form the MicroTask Queue, and you and your colleagues and friends must get in the car right behind them.

There is no asyncracy here, only the same loop back, and there are two kinds of queues. One is the MicroTask Queue, and the Queue formed by you and your colleagues is the MacroTask Queue.

After an event loop returns, the Task in the Task Queue is executed, but the Task has priority. Therefore, tasks in the MicroTask Queue are executed first, and tasks in the MacroTask Queue are executed after execution

A profound

I don’t know if you understand the theory. Here comes the midterm exam!

console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
Copy the code

You don’t have to have a setTimeout with a Promise, a Promise with a whole setTimeout example. Because as long as you understand the code above, it is nothing more than a bus again!

If so much, still can not understand the above picture, then the public account reply [1], hand touch hand guidance!

Event Loop in Node environment

The Event Loop in Node is implemented based on Libuv, which is a new cross-platform abstraction layer of Node. Libuv uses asynchronous and event-driven programming mode, and its core is to provide I/O Event Loop and asynchronous callback. Libuv’s API includes time, non-blocking networks, asynchronous file operations, child processes, and more.

The Event Loop is implemented in Libuv. Therefore, there are two official ways to learn Node Event Loop:

  • Libuv document
  • What is the Event Loop? .

Before learning the Event Loop in Node environment, we should first clarify the execution environment. Node and browser Event Loop are two distinct things and should not be confused. Nodejs events are based on Libuv, while browser event loops are clearly defined in the HTML5 specification.

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ pending Callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ┤ close callbacks │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘Copy the code

Node Event Loop is divided into 6 stages:

  • Timers: performsetTimeout()setInterval()Callback due in.
  • Pending callback: There are a few pending callbacks in the last loopI/OThe callback is deferred until this stage of the round
  • Idle, prepare: Used internally only
  • Poll: The most important stage, executionI/OThe callback,Under the right conditionsWill block at this stage
  • Check: to performsetImmediateThe callback
  • The close callbacks: executioncloseCallback to the event, for examplesocket.on('close'[,fn]),http.server.on('close, fn)

None of the above six phases includes process.nexttick ()(described below)

The overall implementation mechanism is shown in the figure above, and we will expand the description of each stage in detail

Timers phase

The Timers phase performs setTimeout and setInterval callbacks and is controlled by the Poll phase.

In the Timers phase, a minimum heap is used instead of a queue to hold all elements, which is understandable because the callback of timeout is called in the order of the timeout instead of first-in, first-out queue logic. It’s not hard to understand why the Timer phase is on the first execution ladder. The timer is also inaccurate in Node, so that it can be as accurate as possible and its callback function can be executed as quickly as possible.

Here’s an example from the official website:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});
Copy the code

When the event loop enters, it has an empty queue (fs.readfile () has not yet completed), so the timer waits for the remaining milliseconds, and when 95ms is reached, the fs.readfile () callback is added to the polling queue and executed.

When the callback ends, there are no more callbacks in the queue, so the event loop sees that the threshold for the fastest timer has been reached and returns to the Timers stage to execute the timer callback. In this example, you will see that the total delay between the timer being scheduled and the callback being executed will be 105 milliseconds.

Pending callbacks phase

The Pending Callbacks stage is actually the CALLbacks stage of I/O. For example, some TCP error callbacks.

For example, some nix systems want to wait for an error to be reported if TCP socket ECONNREFUSED receives while trying to connect. This will be performed in the Pending Callbacks stage.

Poll phase

The poll phase has two main functions:

  • performI/OThe callback
  • Handles events in a poll queue

When the Event Loop enters the poll phase and there are no tasks to execute in the Timers phase (that is, no timer callbacks), the following two situations will occur

  • If the poll queue is not empty, the Event Loop executes them until they are empty or system-dependent.
  • If the poll queue is empty, one of the following things happens
    • If setImmediate() has a callback to perform, it immediately goes to the Check phase
    • Conversely, if there is no setImmediate() to execute, the poll phase waits for the callback to be added to the queue before executing immediately, which is why we say the poll phase may block.

Once the poll Queue is empty, the Event Loop goes back and checks the tasks in the Timer phase. If so, the callback is returned to the Timer phase.

The check phase

After the poll phase, the setImmediate() callback is added to the check queue, which is a special counter that uses the Libuv API.

The Event Loop usually reaches the poll phase and waits for an incoming link or request, but setImmediate() is specified and the poll phase is idle. The poll phase is aborted and the execution of the check phase begins.

The close callbacks phase

If a socket or event handler suddenly closes/breaks (such as socket.destroy()), the close callback execution occurs in this phase. Otherwise it will be issued through process.nexttick ().

setImmediate() vs setTimeout()

SetImmediate () and setTimeout() are very similar, depending on who invokes it.

  • setImmediateIt is executed after the poll phase, the check phase
  • setTimeoutExecute when poll is idle and the set time reaches, in the timer phase

The order in which timers are executed will vary depending on the context in which they are invoked. If both are called from the main module, the timing is limited by process performance.

For example, if we run the following script that is not in the I/O cycle (that is, the main module), the execution order of the two timers is uncertain because it is constrained by process performance:

// timeout_vs_immediate.js
setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});
Copy the code
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout
Copy the code

If the two calls are moved within an I/O cycle, the immediate callback is always executed first:

// timeout_vs_immediate.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});
Copy the code
$ node timeout_vs_immediate.js
immediate
timeout

$ node timeout_vs_immediate.js
immediate
timeout
Copy the code

So the main advantage of using setImmediate () over setTimeout () is that if any timers are scheduled during the I/O cycle, setImmediate () will always execute before any timers, regardless of how many timers exist.

nextTick queue

You may have noticed that process.nexttick () is not shown in the diagram, even though it is part of the asynchronous API. So he has his own queue: nextTickQueue.

This is because process.nexttick () is not technically part of the Event Loop. Instead, the nextTickQueue is processed after the current operation completes, regardless of the current phase of the current event loop.

If a nextTickQueue exists, it clears all callback functions in the queue and takes precedence over other Microtasks.

setTimeout(() => {
 console.log('timer1')
 Promise.resolve().then(function() {
   console.log('promise1')
 })
}, 0)
process.nextTick(() => {
 console.log('nextTick')
 process.nextTick(() => {
   console.log('nextTick')
   process.nextTick(() => {
     console.log('nextTick')
     process.nextTick(() => {
       console.log('nextTick')
     })
   })
 })
})
// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
Copy the code

process.nextTick() vs setImmediate()

From the user’s point of view, these two names are very confusing.

  • Process. NextTick ()Trigger immediately at the same stage
  • SetImmediate ()Fired in the following iteration or “tick” of the event loop

Looks like these two names should be called! Indeed, so do officials. But they say this is historical baggage that will not change.

Use setImmediate if possible. Because it makes the program more manageable and easier to reason with.

As for why process.nextTick is still needed, it is reasonable to exist. Read the official documentation: Why-use-process-Nexttick.

The Event Loop of Node is different from that of the browser

In the browser environment, the microTask queue is executed after each MacroTask has been executed. In Node.js, microTasks are executed between phases of the event cycle, i.e. tasks in the MicroTask queue are executed after each phase is completed.

The picture above is from sailing on the waves

The last

The final exam is coming

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')})setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')})})Copy the code

Leave your answers in the comments!

reference

  • Tasks, microtasks, queues and schedules
  • Libuv document
  • The Node.js Event Loop, Timers, and process.nextTick()
  • The node’s official website
  • Async /await results are inconsistent between Chrome and Node.
  • Faster asynchronous functions and promises
  • Learn the Event Loop once (Once and for all)
  • This time, thoroughly understand the JavaScript execution mechanism
  • Don’t confuse NodeJS with event loops in browsers

Study and communication

Pay attention to the public number: [full stack front selection] daily access to good articles recommended.

Reply [1] in the public account, join the learning group of the full stack front end and communicate with each other.