Todo List

  • Why does JS have event loops in the browser
  • Do you know the two kinds of tasks in the event loop?
  • Why introduce the concept of microtasks? Can only macro tasks work?
  • Describe the process of executing the event loop in the browser
  • What’s the difference between a Node event loop and a browser event loop
  • async / await
  • Problem.
  • Advanced: Pseudocode implementation

Why does JS have event loops in the browser

Keywords: single thread, multi-thread, synchronous blocking, asynchronous non-blocking

🤓 one thread = one call stack = one thing at a timeCopy the code

A:

JS is single-threaded. Single-threaded means that only one thing can be done at a time. If the current thing is not finished, the thread will be suspended, causing subsequent tasks to block. Some time-consuming script downloads, for example, should not block the user’s subsequent behavior. Hence the concept of asynchronous tasks (callbacks), in which some events that require time are managed by the threads that handle the asynchronous tasks, without blocking subsequent tasks on the main thread.

Because with the addition of multiple threads in parallel, the main thread needs to know the working status of the other threads, “Is the XX task starting? Is the XX task finished? Is there anything unusual?” And so on. If you have two threads operating on a DOM node at the same time, one is deleting the DOM node, and the other is operating on the DOM node, these two operations will conflict with each other, and the browser will not get the correct rendering result that we want.

The main thread needs to coordinate and schedule tasks frequently with multiple threads, so the browser further introduces an EventLoop mechanism to coordinate the work between multiple threads and multiple events.

Do you know the two kinds of tasks in the event loop?

Key words: what is [macro task/micro task] and their respective roles

🤓 Event loop mechanism divides asynchronous tasks into two types: macro task and micro taskCopy the code

A:

Macro tasks: tasks such as entire script, various event callbacks (DOM events, I/O), setTimeout, setInterval, etc.

Microtasks: tasks such as Promise.[then/finally/catch], MutationObserver (DOM change listening/front-end traceback), etc.

Why introduce the concept of microtasks? Can only macro tasks work?

Key words: priority and timeliness of tasks in event loop if you don’t understand this part, I suggest you read the next part first and then come back to understand 💁🏻♀️

A:

No, to solve the synchronous blocking problem of a single thread, asynchronous tasks were introduced and multiple threads collaborated. In order to better schedule tasks and coordinate the work between multiple threads, the event loop mechanism is introduced.

Because the time-consuming tasks are handed to other threads in the way of asynchronous tasks, and in order to ensure the orderly work between multiple threads, only after the main thread completes all the current synchronous tasks in the call stack under the event loop mechanism will it ask which asynchronous tasks can be transmitted back to the main thread for execution.

That is, whether or not your asynchronous task actually takes time, it must be at least the next round of the event loop (a mechanism used to schedule tasks between the main thread and other threads) before its callback function is pushed onto the call stack. This affects events that need to be handled as quickly as possible, because you don’t know how many asynchronous task callbacks you have ahead of you that haven’t been pushed onto the call stack. The concept of microtasks was introduced to deal with time-sensitive, higher-priority tasks in asynchronous tasks.

The callback function of the microtask is pushed onto the call stack before the current event loop ends. In other words, although the macro task and the microtask are encountered in the same round, the macro task’s callbacks will be executed in a subsequent event loop, and the microtask’s callbacks will be executed before the end of the event loop.

Describe the process of executing the event loop in the browser

A:

[Compact version] : empty call stack -> execute the earliest macro task x in the macro task queue -> Execute all microtasks in the microtask queue associated with x -> empty call stack ->…. This repetitive polling mechanism is called an event loop.

[Narrative version] :

Understand how code works

Load JS, encounter task (function call) will be pushed to the call stack, call the stack of tasks by the main thread to execute, after the completion of the execution out of the stack, the call stack trace the entire execution process of JS.

The synchronous task will be executed directly in the call stack, and the asynchronous task will be passed to other threads for processing after being pushed into the stack, and the corresponding asynchronous task function will be off the stack. When asynchronous tasks are processed by other threads, when these tasks meet the conditions for execution of the callback, they are stuffed into the macro task queue and the microtask queue associated with the current macro task according to the task type. Waiting to be pushed onto the call stack for execution.

The prerequisite to understanding how the event loop works is to clear up how our code is executed. The following is a basic example to show how the code is executed.

The code is as follows:

function test() {
    console.log(1)}console.log(2)

test()
Copy the code

The execution is simulated with a simple animation:

Have MP4 version, there is a need to leave a message 🙆🏻♀️

Resource coordination and task scheduling between multiple threads

The event loop will continuously monitor the call stack, and when the call stack is idle (no tasks to execute, only global execution environment in the call stack), it will read the earliest task in the macro task queue and push it into the call stack for execution (the same running process as 1).

All tasks (functions) in the current macro task are completed, that is, before the end of the current batch of macro tasks, the tasks in the microtask queue associated with the current macro task are read and pushed to the call stack in turn for execution (the running process is the same as 1).

Call stack is idle, push the earliest macro task -> macro task internal task execution is complete -> associated microtask queue is all executed, this sequence of actions are finished the call stack is back to the idle state, this is a round of event loop. When the JS engine hears that the call stack is free and that there are still tasks on the macro task queue that can be executed, it will start a new round of events, and so on.

The text is relatively hard and not so readable. Here, I sort out the general event cycle process and copy the scene in the way of pictures 🤯 (there are many pictures, sorry for the traffic T.T).

The above pictures need to leave a message 🙆🏻♀️

What’s the difference between a Node event loop and a browser event loop

Key words: different versions of Node

Node V10 and before: Node.js divides the event loop mechanism into six stages, which are processed in different stages according to the function of macro tasks. Unlike the event loop mechanism of the browser, node.js before V10 will query all the microtasks related to the macro task at this stage only after all the macro tasks at each stage have been executed, and execute them in turn.

After Node V10: After Node.js V10, the event loop mechanism and browser event loop mechanism flow remain the same. After all tasks in a macro task are completed (before the entire macro task is finished), all related microtasks will be executed, and then the next macro task will start.

Run the following code:

setTimeout(() = > {
  console.log(1)
  Promise.resolve().then(() = > console.log(2))},0)

setTimeout(() = > {
  console.log(3)
  Promise.resolve().then(() = > console.log(4))},0)
Copy the code

For Node V10 or earlier: 1 -> 3 -> 2 -> 4 For Node V10 or later: 1 -> 2 -> 3 -> 4

Problem.

For the topic running results, if there are their own understanding can leave a message, I can later a specific analysis 🎸

async / await

Speaking of the main thread and the call stack, there is an obligatory content async/await

The async function returns a Promise object for easy callback management (support chained fn.then.then.blahblah).

// before
async function fn() { 
  return executor
}

// after translate
function fn() { 
  return new Promise(executor)
}
Copy the code

Await is an operator used to form expressions. The result of await XXX depends on what await it is waiting for, which is XXX. If it is not waiting for a Promise, then its calculation is what it is waiting for.

When the await is waiting for a Promise, it blocks the code declared after the current await until the Promise returns. If XXX encounters an error and no exception is caught, the contents after the await will never be executed.

Execution flow encounters await functionXX(): When a Promise

is invoked, functionXX is pushed onto the call stack, the execution flow goes into the function, executes code other than the return Promise once (synchronous task), and the asynchronous operations related to the Promise are handed over to the browser for execution.

  1. The following code runs

Difficulty: ✦ ✦ ✧ ✧ ✧

async function async1() {
  console.log(1)
  await async2()
  console.log(2)}async function async2() {
  console.log(3)}console.log(4)

setTimeout(function () {
  console.log(5)},0)

async1()

new Promise(function (resolve) {
  console.log(6)
  resolve()
}).then(function () {
  console.log(7)})console.log(8)

Copy the code

A:

4
1
3
6
8
2
7
5
Copy the code
  1. The following code runs

Difficulty: ✦ ✦ ✦ ✧ ✧

console.log(1)

/ / 1 s delay
setTimeout(() = > {
  console.log(2)},1000)

async function fn() {
  console.log(3)
  setTimeout(() = > {
    console.log(4)},20)
  return Promise.reject()
}

async function run() {
  console.log(5)
  await fn()
  console.log(6)
}

run()

// It takes about 150ms to execute
for (let i = 0; i < 90000000; i++) {}

setTimeout(() = > {
  console.log(7)

  new Promise((resolve) = > {
    console.log(8)
    resolve()
  }).then(() = > {
    console.log(9)})},0)

console.log(10)

Copy the code

A:

1
5
3
10
4
7
8
9
2
Copy the code
  1. The following code runs

Difficulty: ✦ ✦ ✦ ✦ ✧

function executor(resolve, reject) {
  let rand = Math.random()
  console.log('executor')
  if (rand > 0.5) resolve()
  reject()
}

const p0 = new Promise(executor)

const p1 = p0.then(() = > {
  console.log(1)
  return new Promise(executor)
})

const p2 = p1.then(() = > {
  console.log(2)
  return new Promise(executor)
})

p2.catch(() = > {
  console.log('error')})console.log(2)
Copy the code

A:

/ / to be added
Copy the code

THE END

Thank you for reading 👏🏻 / Thank you for reading 🤙🏼 / thank you for reading 👋 🤙