I used to write a bunch of setTimeout,setImmediate mediate and ask which one does it first. This article is about that, but it’s not about which comes first or which comes last. It is not enough to say that setImmediate executes before setTimeout(fn, 0), because there are situations where setTimeout(fn, 0) does not perform before setImmediate. To thoroughly understand this problem, we need to systematically learn the asynchronous mechanism of JS and the underlying principle. This article will start with the basic concepts of asynchronism and go all the way to the bottom of the Event Loop to get you through the basics of setTimeout,setImmediate, Promise, process.nexttick and so on

  1. JS so-called “single thread” only means that there is only one main thread, not the entire runtime environment is single thread
  2. Asynchronous JS by the bottom of the multithreading implementation
  3. Different asynchronous apis correspond to different implementation threads
  4. Asynchronous threads communicate with the main thread through the Event Loop
  5. When the asynchronous thread completes the task, it puts it into the task queue
  6. The main thread constantly polls the task queue for tasks to be executed
  7. Task queues are distinguished between macro task queues and micro task queues
  8. The microtask queue has a higher priority and the macro task is not processed until all the microtasks have been processed
  9. Promise is a microtask
  10. The Node.js Event Loop is different from the browser Event Loop in that it is staged
  11. SetImmediate and setTimeout(FN, 0) Mediate do not perform any of the preceding callbacks, depending on the phase in which they are registered. SetImmediate does not perform any of the following callbacks: If in the outermost layer or the setImmediate callback, which one executes first depends on the state of the machine at the time.
  12. Process. nextTick is not in any phase of the Event Loop, it is a special API that executes immediately before continuing the Event Loop

Browsers are multi-process

A simplified understanding of browsers

  • Browsers are multi-process, and most browsers on the market are written in C#, C,C++
  • The browser works because the system allocates resources (CPU, memory) to its processes
  • To put it simply, every time you open a Tab, you create a separate browser process
  • Js runs in browsers because of the famous V8 engine

⚠️ Note: It can be understood that browsers are platform carriers and JS is the language running on that platform

Chrome opens multiple tabs inTask managerSee multiple processes (one Tab page, one independent process and one main procedure) in

Synchronous and asynchronous

Synchronous asynchrony simply means that synchronous code is executed in the order in which it is written. Asynchronous code may be different from the order in which it is written and may be executed first. Here’s an example:

- const syncFunc = () => {
+ const syncFunc = () => {
    const starTime = new Date().getTime();
    while (true) {
        if (new Date().getTime() - starTime > 2000) {
            break;
        }
    }
    console.log(2);
}

console.log(1);
syncFunc();
console.log(3);
Copy the code

Inside, the while loop will run for 2 seconds, then print out 2, and finally print out 3. So the code is executed in the same order as we wrote it, syncFunc, syncFunc, syncFunc will print 1, then the call ring will run for 2 seconds, then print 2, and finally print 3. So this code is executed in the same order as we wrote it, which is synchronous code:

Let’s look at an asynchronous example:

const asyncFunc = () = > {
  setTimeout(() = > {
    console.log(2);
  }, 2000);
}


console.log(1);
asyncFunc();
console.log(3);
Copy the code

The output of the above code is:



You can see what we called in betweenasyncFuncThe 2 is the last to be printed because setTimeout is an asynchronous method. His job is to set a timer and wait for the timer to run out before executing the code inside the callback. So asynchrony is the equivalent of doing something, but not right away. Instead, you tell the other person to do it when the XXX condition is met. It’s like setting an alarm on your phone for seven days in the morning before you go to bed at night, giving your phone an asynchronous event that triggers when the time hits 7 a.m.The advantage of using asynchrony is that you only need to set up asynchrony trigger conditions to do other things, so asynchrony does not block the execution of events on the trunk. Especially for a one-thread language like JS, if you do while(true) as we did in the first example, the browser will be stuck until the loop is complete

Browser processes

We all know that JS is single threaded, so how can single thread achieve asynchronous? In fact, “JS is single threaded “just means that there is only one main thread running JS, not the entire runtime environment is single threaded. JS running environment is mainly browser, to everyone is very familiar with the Chrome kernel as an example, he is not only multi-threaded, but also multi-process:



Chrome doesn’t have just one of each type of processes and threads. For example, there are multiple renderers. Each TAB has its own renderer. Sometimes when using Chrome, one of the tabs crashes or doesn’t respond, and the renderer for that TAB may crash, but other tabs don’t use that renderer, they have their own renderer, so the other tabs aren’t affected. That’s why Chrome doesn’t crash with a single page crash, as opposed to the old IE, where a page crash caused the entire browser to crash.

For front-end engineers, the main concern is still the rendering process, so let’s take a look at what each thread does

The Browser process is the main process of the Browser. It is responsible for coordinating and controlling the Browser

  • Responsible for browser interface display and user interaction. Forward, backward, etc
  • Responsible for page management, creating and destroying other processes
  • Draw an in-memory Bitmap from the Renderer process onto the user interface
  • Network resource management, download, etc

Each type of plug-in corresponds to one process. A maximum of one GPU process can be created only when this plug-in is used. The browser rendering process is used for 3D drawing. The main functions are page rendering, script execution, event processing and so on

Advantages of multi-process browsers

  • Advantages of multiple processes over single processes, avoiding a single page crash affecting the entire browser
  • Avoid third-party plug-in crashes affecting the entire browser
  • Multi-process makes full use of multi-nuclear advantages
  • It is convenient to use sandbox model to isolate processes such as plug-ins and improve browser stability

Rendering process

The renderer process (multithreading) contains threads (also called browser kernel: render kernel + JS kernel + etc.) that are the browser kernel

The GUI thread

  • Responsible for rendering browser interfaces, parsing HTML, CSS, building DOM trees and RenderObject trees, layout and drawing, etc
  • This thread executes when the interface needs to be repainted or when some operation causes reflow

⚠️ Note: The GUI rendering thread and the JS engine thread are mutually exclusive, the GUI thread is suspended (frozen) while the JS engine is executing, and GUI updates are stored in a queue until the JS engine is idle

JS engine thread

  • This thread is the main thread responsible for executing JS, and the famous Chrome V8 engine runs in this thread
  • Is there only one JS thread running on a Tab page (renderer process) at any given time

⚠️ Note: This thread is mutually exclusive with the GUI thread. The reason for the mutual exclusion is that JS can also manipulate the DOM, and if the JS thread and the GUI thread manipulate the DOM at the same time, the result will be confusing and it will not be known which result to render. The consequence of this is that if JS runs for a long time, the GUI thread will not execute and the entire page will feel stuck. So the long while(true) synchronization code of our original example should never be allowed during actual development

Timing trigger thread

  • The legendary setTimeout and setInterval thread, so “single-threaded JS” can achieve asynchronous.
  • Browser timing counters are not counted by JavaScript engines (because JavaScript engines are single-threaded, blocking threads can affect timing accuracy)
  • So timing is done by a separate thread and timing is triggered (when the timing is done, it is added to the event queue to wait for the JS engine to be idle)
  • Note that the W3C specification in the HTML standard requires that setTimeout intervals below 4ms be counted as 4ms. So, even if setTimeout is set to 0, it’s actually 4ms
  • The timer thread is just a function of timing. It does not actually execute the callback when the time is up. It is the main JS thread that actually executes the callback. So when the time is up, the timer thread will pass the callback event to the event-triggering thread, which will then add it to the event queue. Eventually the JS main thread takes the callback from the event queue to execute. The event trigger thread not only puts timer events into the task queue, but also puts other events that meet the criteria into the task queue

Event trigger thread

  • To control the event loop (understandably, the JS engine is too busy to handle itself, so the browser needs to open another thread to help)
  • When the JS engine executes a code block such as setTimeOut (or other threads from the browser kernel, such as mouse clicks, AJAX asynchronous requests, etc.), it adds the corresponding task to the event thread
  • When the corresponding event meets the trigger condition is triggered, the thread will add the event to the end of the queue to be processed, waiting for the JS engine to process
  • Note that due to the single-threaded nature of JS, these events in the queue must be queued up for the JS engine to process (they are executed when the JS engine is idle)

Asynchronous HTTP request threads

  • After the XMLHttpRequest connects, the browser opens a new thread request
  • This thread is responsible for processing asynchronous Ajax requests, and when the request is complete, it notifies the event-triggering thread, which then puts the event into an event queue for the main thread to execute
  • When a state change is detected, if a callback function is set, the asynchronous thread generates a state change event and places the callback in the event queue. It’s executed by the JavaScript engine

Therefore JS asynchronous implementation depends on the browser multi-threading, when he met asynchronous API, will this task to the corresponding threads, when the asynchronous API callback conditions, the corresponding thread and thread through the trigger events put this event in a task queue, and then the main thread from the task queue events continue to execute. We’ve talked a lot about the task queue, which is actually an Event Loop. Let’s go through it in detail

Tease out the relationship between threads in the browser kernel

Load event and DOMContentLoaded event

DOMContentLoaded: only when the DOM is loaded, not including the stylesheets. Load: All the DOM, stylesheets, scripts, images on the page are loaded.

JS blocks page loading

JS will block the page if it takes too long to execute. For example, if the JS engine is doing a huge amount of computation, updates to the GUI, if any, will be queued up for execution when the JS engine is idle. However, due to the huge amount of computation, it is likely that the JS engine will idle for a long time and the natural page rendering load will be blocked

Whether CSS loading blocks DOM tree rendering

This is the case where the header introduces CSS, since CSS is asynchronously downloaded by a separate download thread. then

  • CSS loading does not block DOM tree parsing (DOM builds as usual for asynchronous loading)
  • But it blocks the render tree until the CSS is loaded because the render tree needs CSS information.

The render tree may have to be redrawn or reflow after the CSS is loaded because you may change the style of the DOM nodes below while loading the CSS, causing unnecessary wear and tear if the CSS is not blocked. So it’s better to parse the STRUCTURE of the DOM tree first, finish what you can do, and then render the render tree according to the final style after the CSS is loaded

Normal and composite layers

  • The normal document flow can be understood as a compound layer, and absolute(fixed) is also on the same compound layer as the normal document flow by default
  • If A is a composite layer and B is above A, then B will also be implicitly converted to a composite layer. Special attention should be paid to this point. In GPU, each composite layer is drawn separately, so it does not affect each other
  • For some animations, in order to prevent DOM updates and redrawing of the entire page, a composite layer will be created by means of translate3D to save performance (hardware acceleration).

Event Loop

The so-called Event Loop is the Event Loop. In fact, it is a process for JS to manage the execution of events. The specific management method is determined by its specific running environment. Currently, there are two main running environments for JS

  • The browser
  • Node.js

There are some differences in the Event Loop between the two environments, which will be discussed separately

Event Loop for the browser

An event loop is a loop that asynchronous threads use to communicate and execute cooperatively. Threads also have a common data area for exchanging messages, called the event queue. After each asynchronous thread completes its execution, the thread triggers the callback event to the event queue, and the main thread checks the queue for new work every time it finishes its work. If there is any new work, it takes it out and executes it. A flow chart looks like this:



The process is explained as follows:

  1. Each time the main thread executes, it looks at whether it is executing a synchronous task or an asynchronous API
  2. The synchronization task continues until it is complete
  3. When you encounter an asynchronous API, you hand it to the corresponding asynchronous thread and continue to perform the synchronization task yourself
  4. The asynchronous thread executes the asynchronous API and, when it’s done, places the asynchronous callback event on the event queue
  5. The main thread checks the event queue to see if there are any tasks when the synchronization task in hand is finished
  6. The main thread finds a task in the event queue and executes it
  7. The main thread repeats the above process

Wrong timer

In the Event Loop process, there are some hidden holes. The most typical problem is that the synchronization task is always executed first, and then the callback in the Event queue is executed. This feature directly affects the execution of the timer. Let’s think about the process we started with the 2-second timer:

  1. The main thread executes synchronization code
  2. When you encounter setTimeout, pass it to the timer thread
  3. The timer thread starts timing and the notification event triggers the thread in 2 seconds
  4. The event triggers the thread to put the timer callback into the event queue, and the asynchronous process ends there
  5. If the main thread is free, the timer callback is taken out and executed; if not, the callback is kept in the queue

If the main thread is blocked for a long time, the timer callback will not be executed. If it is executed, the timer callback will not be executed properly.

const syncFunc = (startTime) = > {
    const time = new Date().getTime();
    while (true) {
        if (new Date().getTime() - time > 5000) {
            break; }}const offset = new Date().getTime() - startTime;
    console.log(`syncFunc run, time offset: ${offset}`);
}

const asyncFunc = (startTime) = > {
    setTimeout(() = > {
        const offset = new Date().getTime() - startTime;
        console.log(`asyncFunc run, time offset: ${offset}`);
    }, 2000);
}


const startTime = new Date().getTime();
asyncFunc(startTime);
syncFunc(startTime);
Copy the code

The result is as follows:



It can be seen from the result that although asyncFunc is called first and written to be executed after 2 seconds, syncFunc execution time is too long, reaching 5 seconds. Although asyncFunc has entered the event queue after 2 seconds, the main thread has been executing synchronous code and has not been available. The timer callback will not be executed until the synchronization code completes 5 seconds later.So again, don’t write code that’s tied up in the main thread for too long

Introducing microtasks

In the previous flow chart, I simplified the event queue to make it easier to understand. In fact, there are two types of events in the event queue: macro tasks and micro tasks.Microtasks have a higher priority. When the event loop traverses the queue, the microtask queue is checked first, and if there are any tasks in it, they are all executed, and then a macro task is executed. Before executing each macro task, check whether there is any task in the microtask queue. If so, execute the microtask queue first. So the complete flow chart is as follows:

Here are a few things to note in the figure above:

  1. An Event Loop can have one or more Event queues, but only one microtask queue
  2. The microtask queue is re-rendered once it has all been executed
  3. Each macro task is re-rendered after execution
  4. RequestAnimationFrame is in the render phase and is not in the microtask queue or macro task queue

So to know at what stage an asynchronous API executes, we need to know whether it is a macro task or a micro task,

Common macro tasks are

  1. Script (can be understood as the outer synchronization code)
  2. setTimeout/setInterval
  3. setImmediate(Node.js)
  4. I/O
  5. UI events
  6. postMessage

Common microtasks are

  1. Promise
  2. process.nextTick(Node.js)
  3. Object.observe
  4. MutaionObserver

Watch out for promises in these event types. They are microtasks, meaning they run in front of timers. Here’s an example:

console.log('1');

setTimeout(() = > {
    console.log('2');
}, 0);

Promise.resolve().then(() = > {
    console.log('5');
})

new Promise((resolve) = > {
    console.log('3');
    resolve();
}).then(() = > {
    console.log('4');
})

for (let i = 0; i < 10; i++) {
    console.log("This is the output event in for.")}/* The output of this code is 1, 3 this is the output event in for this is the output event in for this is the output event in for this is the output event in for This is the output event in for and this is the output event in for 5, 4, 2 star/theta
Copy the code
  1. Print 1 first, there’s nothing to say about that, synchronous code executes first, right
  2. Console. log(‘2’) in setTimeout, setTimeout is a macro task, and “2” enters the macro task queue
  3. Console. log(‘5’) in promise. then, enters the microtask queue
  4. Console. log(‘3’) in the argument to the Promise constructor is actually synchronized code, printed directly
  5. Console. log(‘4’) in then, it enters the microtask queue and performs the microtask first while checking the event queue
  6. Synchronous code run result is “1,3”
  7. Then check the microtask queue and print “5,4”
  8. Finally, execute the macro task queue and print “2”
To strengthen practice
async function async1() {
  console.log("async1 start") / / 2
  await async2()
  console.log("async1 end")  / / 6
}

async function async2() {
  console.log("async2") / / 3
}

console.log("script start") / / 1

setTimeout(() = > {
  console.log("Timer")  / / 8
}, 0)

async1()

new Promise(function (resolove) {
  console.log("Promise start")  / / 4
  resolove()
}).then(() = > {
  console.log("Promise then") / / 7
})

console.log("script end") / / 5

/* script start async1 start async2 Promise start script end async1 end Promise then timer */
Copy the code

Node. Js Event Loop

Node.js is the JS running on the server side. Although it also uses V8 engine, its service purpose and environment are different, leading to some differences between its API and native JS. Its Event Loop also needs to deal with some I/O, such as new network connections, so it is also different from the Event Loop of the browser. Node’s Event Loop is staged

Before Node V10, microtasks and macro tasks were executed in Node in the following order:

  1. Complete all missions in a phase
  2. Finish executing the nextTick queue
  3. Then execute the contents of the microtask queue

In Node V10 and earlier, microtasks are executed between stages of the event loop, i.e., when one stage is completed, the task in the microtask queue is executed:

⚠ ️Note:The first thing to know is that the order of execution varies depending on the Node version. Since Node V11, the principle of the event loop has changed and the order of execution in the browser has changed,Each macro task completes the microtask queue

  1. Timers: Executes the callback of setTimeout and setInterval
  2. Pending Callbacks: I/O callbacks that are deferred until the next iteration of the loop
  3. Idle, prepare: used only in the system
  4. Poll: Retrieves new I/O events and performs I/ O-related callbacks. In fact, almost all asynchrony is handled in this phase, except for a few other phases
  5. Check: Perform setImmediate here
  6. Close callbacks: some closed callbacks, such as socket.on(‘close’,…)

Each phase has its own FIFO queue, and only when the events in this queue are completed or the phase’s upper limit is reached, does it proceed to the next phase. Between each event loop, Node.js checks to see if it is waiting for any I/O or timer, and if it isn’t, the program shuts down and exits. The intuition is that if a Node application only has syncing code, it will exit after you run it on the console. The poll phase is not always followed by the check phase. After the poll queue is completed, if there is no setImmediate mediate but a timer expires, the player will loop back to the timer phase:

SetImmediate and setTimeout

SetImmediate Mediate Does not mediate setImmediate Mediate Mediate Mediate setImmediate Does not mediate in an asynchronous flow.

console.log('outer')

setTimeout(() = > {
  console.log('setTimeout')},0)

setImmediate(() = > {
  console.log('setImmediate')})Copy the code

The above code runs as follows:



Like we said earlier, setImmediate executes first. Let’s go through the process:

  1. The outer layer is a setTimeout, so its callback is already in the Timers phase
  2. Handle the setTimeout inside because the callback is actually applied to the next timers stage because the timers are executing
  3. Handle setImmediate and add its callback to the check phase
  4. After the timers are displayed in the outer stage, the pending Callbacks, Idle, prepare, and poll queues are empty
  5. At the check phase, setImmediate’s callback is detected and taken out to execute
  6. And then close callbacks, queue space-time, skip
  7. Again, the Timers phase executes our console

But note that console.log(‘setTimeout’) and console.log(‘setImmediate’) are both wrapped in a setTimeout. What if we just write it in the outer layer? The code rewrite is as follows:

console.log('outer')

setTimeout(() = > {
  setTimeout(() = > {
    console.log('setTimeout')},0)

  setImmediate(() = > {
    console.log('setImmediate')})},0)
Copy the code

Let’s run it to see what it looks like:



SetTimeout = setTimeout = setTimeout



SetImmediate Mediate Does not mediate Mediate setImmediate Does not mediate setImmediate Does not mediate setImmediate does not mediate setImmediate does not mediate

  • Node.js setTimeout(fn, 0) is forced to setTimeout(fn, 1), as explained in the official documentation.
  • The minimum time limit for setTimeout in HTML 5 is 4ms

Now that we have the principle, let’s go through the process:

  1. The outer synchronous code is executed all at once, and the asynchronous API is plugged into the corresponding stage
  2. SetTimeout is set to 0 ms, but node.js forces it to 1 ms, and inserts The Times phase
  3. SetImmediate Mediate Mediate Enters the Check phase
  4. After the synchronization code is executed, enter the Event Loop
  5. Enter The Times phase and check whether the current time has passed 1 millisecond. If the time has passed 1 millisecond, the setTimeout condition is met and the callback is executed. If the time has not passed 1 millisecond, the callback is skipped
  6. Skip the empty phase to the Check phase and perform the setImmediate callback

SetImmediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Mediate Each time we run the script, the machine state may be different, leading to a 1-millisecond gap between setTimeout and setImmediate. However, this occurs only when timers are not in the timers stage. As in our first example, setTimeout must wait for the next loop because it is already in timers, so setImmediate must execute first. The same applies to other POLL-phase apis, such as:

var fs = require('fs')

fs.readFile(__filename, () = > {
    setTimeout(() = > {
        console.log('setTimeout');
    }, 0);
  
    setImmediate(() = > {
        console.log('setImmediate');
    });
});
Copy the code

The setTimeout and setImmediate mediate mediate mediate are used in the readFile callback, which is an I/O operation and is located in the poll phase. Therefore, the readFile mediate can only enter the next timers phase. SetImmediate, however, runs through the subsequent check phase, so setImmediate must run first and then checks timers before running setTimeout. Similarly, if the two of them are not in the outer layer, but rather in the setImmediate callback, the effect is similar. Here is the code:

console.log('outer');
setImmediate(() = > {
  setTimeout(() = > {
    console.log('setTimeout');
  }, 0);
  setImmediate(() = > {
    console.log('setImmediate');
  });
});
Copy the code

The reason is similar to what is written in the outermost layer, because setImmediate is already in the Check phase. The loop inside starts with timers and looks for the setTimeout callback and executes it if 1 millisecond has passed, and setImmediate if not

process.nextTick()

Process.nexttick () is a special asynchronous API that does not belong to any Event Loop phase. In fact, when Node encounters this API, the Event Loop will not continue at all. It will immediately stop and execute process.nexttick (), and the Event Loop will continue after this execution. Let’s write an example:

var fs = require('fs')

fs.readFile(__filename, () = > {

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

  setImmediate(() = > {
    console.log('setImmediate');

    process.nextTick(() = > {
      console.log('nextTick 2');
    });
  });

  console.log('-- -- -- -- -- -- -- -- --');
  process.nextTick(() = > {
    console.log('nextTick 1');
  });
});
Copy the code

This code is printed as follows:

Let’s go through the process:

  1. Our code is basically inside the readFile callback, and when it executes itself, it’s already in the poll phase
  2. When you encounter setTimeout(fn, 0), which is actually setTimeout(fn, 1), insert the timers stage behind it
  3. SetImmediate Encounters Mediate, then pushes through the check phase
  4. When it encounters nextTick, execute immediately, print ‘nextTick 1’
  5. And then the check phase outputs ‘setImmediate’, which leads to the nextTick, and then outputs ‘nextTick 2’.
  6. To the next timers phase, print ‘setTimeout’

This mechanism is similar to the microtasks we discussed earlier, but not exactly the same. For example, when there is both nextTick and Promise, nextTick must execute first because the nextTick queue has higher priority than the Promise queue. Here’s an example:

const promise = Promise.resolve()

setImmediate(() = > {
    console.log('setImmediate');
});

promise.then(() = > {
    console.log('promise')
})

process.nextTick(() = > {
    console.log('nextTick')})Copy the code

The code results are as follows:

Questions about macro and micro tasks

The interview questions 1

async function async1() {
  console.log('async1 start')
  await async2()            / /! Focus on the new Promise (() = > {}), then (() = > console. The log (' async1 end '))
  console.log('async1 end') // The task is in. Then
}
async function async2() {
  console.log('async2')}console.log('script start')
setTimeout(function () {
  console.log('setTimeout')},0)
async1()
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promsie2')})console.log('script end')

/* script start async1 start async2 promise1 script end async1 end promsie2 setTimeout */
Copy the code

The interview questions 2

console.log('start')

setTimeout(() = > {
  console.log('children2')
  Promise.resolve().then(() = > { // Put the promsie resove directly out and put the. Then microtask callback into the task queue
    console.log('children3')})},0)

new Promise(function (resolve) {
  // Macro task code
  console.log('children4')
  setTimeout(() = > {
    console.log('children5') // In this promise, resolve must go out, then the microtask callback will be added to the task queue; So in the first round,.then was not added to the microtask queue
    resolve('children6')     // This sentence is executed to queue. Then microtasks
  }, 0);
}).then(res= > {   // Microtask code
  console.log('children7') 
  setTimeout(() = > {
    console.log(res)
  }, 0)})Start children4 children2 children3 children5 children7 children6 The whole code macro task has no microtask start => chilren4 executes the second macro task children2; Trap 1: In a Promise, resolve must go out, then the microtask callback will be added to the task queue. So in the first round,.then is not added to the microtask queue */
Copy the code

The interview question 3

const p = function () {
  return new Promise((resolve, reject) = > {
    // Because of p(), this is executed as the first macro task
    const p1 = new Promise((resolve, rejct) = > {
      setTimeout(() = > { // Trap: A promise can only be resolved once. Once the state is changed, it cannot be withdrawn
        resolve(1)},0)
      resolve(2)
    })
    p1.then(res= > {
      console.log(res)
    })
    console.log(3)
    resolve(4)
  })
}

p().then(res= > {
  console.log(res)
})
console.log('end')

/* 3 end 1 => end 2 4 trap: a promise can only resolve once. If the state is changed, it cannot be undone
Copy the code

At the end of the article

⚠️ article first in [a wisp of breeze], welcome to pay attention to the article part of the picture from the network, if infringement, please contact to delete and you know each other, not deep sea of clouds. I wish you in the front of the field invincible, together to the peak of life

Reproduced documents