JS is single threaded

One of the biggest features of the JavaScript language is single-threaded, but by single-threaded I mean the main thread is single-threaded. So why is JS single threaded? JS is mainly used to manipulate the DOM. If there are two threads, one adding content to the DOM and the other deleting content from the DOM, which one should the browser use? So to avoid complexity, JavaScript has been single-threaded since its birth.

Synchronous and asynchronous

Synchronous and asynchronous focus on message notification mechanisms

  • 1) Synchronization does not return until the result is returned. Once the call returns, the value is returned. The caller actively waits for the result of the call.
  • 2) Asynchracy is when a call is made and the caller does not get the result immediately. Instead, the caller processes the call through a status or callback function.

Task queue

  • becauseJavaScriptIt’s single-threaded. This means that all tasks need to be queued so that the first task can be completed before the next task can be executed. The first one takes a long time, and the second one has to wait. But IO devices (e.gajaxNetwork request) is very slow, the CPU has been a state, so it is very unreasonable.
  • Therefore, the main thread can suspend the waiting task and run the next task first, regardless of the IO device. Wait until the IO device returns the result, then go back and continue the pending task. So there are synchronous tasks and asynchronous tasks.

A synchronization task is executed on the main thread. The next task can be executed only after the previous task is completed. Asynchronous tasks are tasks that do not enter the main thread but enter the task queue. The tasks in the task queue can enter the main thread only after the main thread is complete.

Event Loop in browser

From the image above:

  1. The main thread generates heap and stack when it runs
  2. The code in the stack calls various external apis that add various events (click, load, done) to the “task queue”
  3. As soon as the code in the stack completes, the main thread reads the “task queue” and puts the events in the queue into the execution stack.
  4. The main thread continues to execute, and when it calls the external API, it is added to the task queue, and when the main thread completes execution, it is added to the main thread.
  5. The whole process is circular.

The Node of the Event Loop

Node.js is also a single-threaded Event Loop, but it operates differently from the browser environment.

According to the diagram above, Node.js works as follows:

  1. The written JavaScript scripts are handed over to the V8 engine for parsing
  2. The parsed code calls the Node API, which is handed over to the Libuv library
  3. The Libuv library assigns different tasks to different threads, forming an Event Loop that asynchronously returns the results of the tasks to the V8 engine
  4. The V8 engine returns the results to the user

In addition to setTimeout and setInterval, Node.js provides two other “task queue” related methods: process.nextTick and setImmediate.

The process.nextTick method fires the callback function —- at the end of the current “execution stack” —- before the next Event Loop (the main thread reads the “task queue”). That is, the task it specifies always takes place before all asynchronous tasks. The setImmediate method adds an Event to the end of the current “task queue,” which means that the task it specifies is always executed on the next Event Loop, much like setTimeout(fn, 0).

英文原文 : When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

Event loops are initialized when Node.js starts, and each event loop contains six phases in the following order

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code
  • Timers stage: This stage performs setTimeout(callback) and setInterval(callback) scheduled callback.
  • I/O Callbacks: Execute callbacks other than close events, callbacks set by timers(setTimeout, setInterval, etc.), and callbacks set by setImmediate().
  • Idle, prepare: Used only internally.
  • Poll phase: Fetch new I/O events, where node will block under appropriate conditions;
  • The Check phase: Executes the setImmediate() set callbacks;
  • Close Callbacks phase: Callback such as socket.on(‘ close ‘, callback) is executed in this phase.

Each phase has a FIFO queue with callbacks. When the Event loop runs to a given phase, node will execute the FIFO queue for that phase. When the queue callback runs out or the number of callbacks executes exceeds the upper limit for that phase, the Event loop will proceed to the next phase. ** Process.nexttick () is not executed at any stage of the Event loop, but rather in the middle of each phase switch, before switching from one phase to the next.

Macro and micro tasks

Tasks can be divided into macro tasks and micro tasks

Common macro and micro tasks:

  1. Macro-task: setTimeout.setInterval.setImmediate.I/O
  2. Micro-task:process.nextTick, nativePromise(Some dopromisewillthenMethod in macro task),Object.observe(abandoned),MutationObserver

Look at the following example:

console.log('script start');

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

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

console.log('script end');
Copy the code

What is the order of execution of the above code? Why is that?

script start
script end
promise1
promise2
setTimeout
Copy the code

Tasks are stacked and executed in strict chronological order, so browsers allow JavaScript internal tasks and DOM tasks to be executed in an orderly fashion. When one task finishes, the browser can rerender the page before the next task starts. Each task needs to be assigned, such as from a user click to a click event, rendering an HTML document, and setTimeout as in the above example.

Based on the event loop described earlier, setTimeout will assign a new task to the Event loop after the delay expires, rather than immediately executing it, so the setTimeout callback will wait for all previous tasks to complete before running. This is why setTimeout is printed after Script end, because Script end is part of the first task and setTimeout is a new task.

Microtasks are typically tasks that need to be executed immediately after the execution of the current task, such as a response to a series of tasks, or tasks that need to be executed asynchronously without assigning a new task to reduce the performance overhead.

A microtask task queue is a queue independent of a task task queue. The microtask will be executed after the execution of each task. Each microtask created in a task is added to the microtask queue, which is added to the end of the current queue and processes all tasks in the queue in sequence.

Each time a Promise is decided (or rejected), its callback function is added to the microtask queue as a new microtask. This also ensures that promises can be executed asynchronously. So when we call.then(resolve, reject), a new microtask is immediately generated and added to the queue, which is why promise1 and promise2 are printed after script end. Because tasks in the microtask queue must wait for the current task to finish, promise1 and promise2 output before setTimeout, because setTimeout is a new task, Microtasks are executed after the current task ends and before the next task begins.

Reference:

  1. The Node.js Event Loop, Timers, and process.nextTick()
  2. More on the Event Loop
  3. The Node.js Event Loop, Timers, and process.nextTick()
  4. In-depth understanding of JavaScript event loops (PART 2) – Task and MicroTask