background


About half a year ago, I saw a front end article on wechat. The article begins with an interview question and asks the reader to write an answer. At first glance, it’s all console.log() code, but the only thing I find tricky is the addition of setTimeout and Promise. I did not think too much, full of confidence began to do the problem. Sure enough, the exact opposite of the correct answer. Looking at the author’s subsequent explanation, it is the first time to contact the two concepts of macro task and micro task. One thing I understood at the time was that both concepts are used to distinguish asynchronous tasks. Then I spent more than half a year, until I saw another problem a few days ago. The answer is half off, and the understanding of macro and micro tasks in my mind becomes blurred again. Reflect, or at the beginning of the understanding of knowledge is not deep, not into their own things. This is not a solid foundation of performance.

Read a lot of peer article on nuggets, but not their own understanding, in this summary of their understanding.

Characteristic of JS: single thread


Before we talk about macro tasks and microtasks, let’s talk about some basic concepts. JavaScript was born in 1995. The original purpose of JavaScript was to bring web pages to life, to execute some logic on them, to increase user interaction with web pages, and to make them more “intelligent”. Because the original purpose is very simple, so it is destined that JS is not complex, nor is it allowed to be very complex. On this basis, a characteristic of JS is highlighted, that is, single thread.

What does single thread mean? This means that the JS engine can only handle one thing at a time. If there are more than one thing to handle, the JS engine will do it one at a time. We can think that JS engine is like Guo Jing, simple and simple, can’t learn zhou Botong’s left and right mutual stroke. If there are single-threaded languages, there must be multithreaded languages. Yes, basically the backend language is multi-threaded, like Huang Rong, multi-tasking, quirky.

This world is objective, get some things correspondingly will lose some things. Judge and accept the good and the bad for yourself. The advantage of single-threaded JS is that it’s simple. You don’t have multiple threads modifying the DOM at the same time, which would make your web pages messy.

In fact, JavaScript single-threaded refers to the browser is responsible for the interpretation and execution of JavaScript code only one thread, namely the JS engine thread, but the browser rendering process is provided with multiple threads, as follows:

  • JS engine thread
  • Event-triggered thread
  • Timed trigger thread
  • Asynchronous HTTP request threads
  • GUI rendering thread

Synchronous and asynchronous events


Here’s a look at synchronization and asynchrony, two concepts that are separate from the programming language. It’s a reflection of what happens in the real world. Synchronization is something that can be done immediately, asynchronous is something that can’t be done in a while.

For example, if you open your book, this is a synchronous event. You can do it right away, without the slightest delay or uncertainty. If you look at kettle boiling, this is an asynchronous event. Because you can’t control the water to boil, you have to wait for the water to boil. By contrast, asynchronous events have more “uncertainty”, or introduce “variables”, than synchronous events. Reflected on the code, the first line of code below is the sync code, printing out the 1 directly. The second line of code is the timer, setTimeout, which is an asynchronous event because it needs to wait for a set time of 200ms to finish printing. Even if this time is 0, it will be classed as an asynchronous event by the JS engine. Of course, Ajax requests are typically asynchronous events, waiting for a response from the server.

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

There is a concept of a callback function in asynchronous events because asynchronous events are not done in a short time. So what do you need to do when the asynchronous event is complete? Like the kettle boiling water above, the water boiled, I want to drink, this is a callback logic. I can also wait for the water to boil and I want to do laundry, which is also a callback logic. So for asynchronous events, you need to register a callback function that represents the processing that needs to be done after the asynchronous event is completed. The hidden question here is who notifies the JS engine when the asynchronous event completes? The answer is that the browser thread is responsible for listening for asynchronous event completion.

At the beginning, we said that JS is a single threaded language, which can only do one thing at a time, so it is very simple to encounter synchronous events, no nonsense, directly do. However, when it comes to asynchronous events, it is a little more complicated, and the JS engine needs to consider the following issues:

  • What do I do when I encounter an asynchronous event?
  • How do I know when an asynchronous event is complete?
  • If there are multiple asynchronous events, which one is executed first?

In order to solve the problem of the execution order of asynchronous events, the JS engine produces a mechanism called Event Loop. Through this mechanism, the JS engine processes the asynchronous events, one at a time.

Event Loop


The first thing to make clear is that at the highest level, synchronous events execute before asynchronous events. How to execute multiple asynchronous events requires an “event loop” mechanism to handle the sequence of multiple asynchronous events.

Reading:

  • Synchronous and asynchronous tasks go to different “venues”, synchronously to the main thread and asynchronously to the Event Table and register functions
  • When the specified thing is done, the Event Table moves the function to the Event Queue
  • If the task in the main thread is empty after execution, it will go to the Event Queue to read the corresponding function and enter the main thread for execution
  • This process repeats over and over again, known as an Event Loop.

So how do we know that the main thread execution stack is empty? The JS engine has a monitoring process that continuously checks to see if the main thread execution stack is empty. Once empty, it checks the Event Queue to see if any functions are waiting to be called.

Macro tasks and microtasks


As shown in the flowchart above, the asynchronous task first enters the Event Table and registers the callback function. When the specified task is completed, the Event Table moves the function to the Event Queue. The problem is that different asynchronous events go to different Event queues. Here asynchronous events are further divided into macro tasks and microtasks.

For example, go to a bank to deal with business, need to take a number to wait to call oneself above all. It would say something like, “Your number is XX, there are XX people ahead of you.” “And so on. Because a teller at a window can only handle one customer for business at a time, each customer for business is a macro task for a teller. When the teller has finished dealing with the current customer’s problem, he chooses to receive the next customer and announces the call number, which is the start of the next macro task. Like the Excel concept of “macro,” here “macro” has a whole concept.

So when you have multiple macro tasks together you can say that you have a task queue right here, and that queue is all the customers in the current bank. Tasks in the queue is asynchronous operation has been completed, rather than the said registered an asynchronous task will be placed in the task queue, like numeral in the bank, if you don’t call to your time, then your current plate scrapped, teller would choose to skip to the next customer business processing, waiting for you to come back later also need to take a number. And a macro task in the process of execution, it is possible to add some micro tasks, just like in the counter for business, an old man in front of you may be in the deposit, after the deposit this business, the teller asked uncle there are no other business to do. My uncle said that he would like to deal with financial management, so the teller would continue to deal with financial management for my uncle, instead of directly dealing with the next customer’s business. So it was your turn to deal with business, will be temporarily added because of the old man “wealth management business” and put back. Maybe old uncle still want to do a credit card again after handling financial affairs? Or buy some more coins? Whatever needs the teller can handle for her, she will do them before handling your business, which can be considered microtasks.

This shows that the microtask context for the same batch is the same, depending on the current macro task. So when a macro task is completed, the main thread will ask if there are any microtasks that need to be processed. The next macro task will start only after all the current microtasks have been processed.

Here’s what I got wrong:

console.log(1) 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() 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) // 1 5 3 10 4 7 8 9 2Copy the code

There are a few concepts that need to be clarified before doing this problem:

  • Among the technologies based on microtasks are MutationObserver, Promise, and many other technologies developed on the basis of promises. Resolve () and await fn() in this case are microtasks.
  • The callbacks registered in the Promise’s THEN are asynchronous, but the Promise itself is synchronous. That is to say,new PromiseThe code executed during the instantiation process is synchronized, whilethenCallbacks registered in are executed asynchronously.
  • Every time the main thread executes on the empty stack, the engine prioritises the microtask queue, regardless of the arrival time and the order in which the macro task is placed, and processes all the tasks in the microtask queue before processing the macro task.
  • All the asynchrony that goes into is that part of the code in the event callback

The following is a step-by-step analysis:

  • The first line of code console.log(1) is the synchronization task and prints 1 directly.

  • The second line of code touches the setTimeout, asynchronous task and belongs to the macro task. First, the setTimeout is put into the Event Table and the callback function is registered. After 1000ms, the callback function is put into the macro task queue. So the macro task queue is temporarily empty

    Event Table Macro task queue Microtask queue
    setTimeout1(1000ms) empty empty
  • The next step is to execute run(), although the run function is preceded by the async keyword to indicate that there are asynchronous events inside. However, it does not affect other synchronized code execution of the function. The code console.log(5) executes, printing 5. Then I hit “await” keyword, continue to execute function fn, code console.log(‘3’) executes, print 3. We hit setTiemout again and put it in the Event Table. Again, after setTimeout, promise. reject belongs to a microtask, put it in the microtask queue.

    Event Table Macro task queue Microtask queue
    setTimeout1(1000ms) empty Promise.reject()
    setTimeout2(20ms) empty empty

Note here: since fn does not complete, the code after awit fn() will not execute, and the browser will continue with the for loop.

  • Then execute the for loop. Since the for loop is a synchronous task, the main thread’s job is to execute the for loop logic. Even if the loop is empty, it will take 150ms. After 150ms, the for loop is completed, and the 20ms setTimeout in the Event Table has run out of time. The callback function is put into the macro task queue.

    Event Table Macro task to column Microtask queue
    setTimeout1(1000ms) setTimeout2(20ms) Promise.reject()
    empty empty empty
  • The main thread continues to execute code, again bumping into setTimeout, again putting it in the Event Table.

  • Next, execute the code console.log(10) and print 10. The macro task is complete. The script is regarded as a macro task.

  • After the macro task is finished, the main thread asks if there are any microtasks that need to be executed. In this case, promise.reject () exists in the microtask and executes the task. The await fn() in the function run then completes, noting that since the Promise to the right of the await returns reject, nothing else is executed. The microtask execution is complete.

    Event Table Macro task queue Microtask queue
    setTimeout1(1000ms) setTimeout2(20ms) empty
    empty setTimeout3(0ms) empty
  • To start the next macro task, execute the setTimeout2 callback function. Note that all the callbacks that enter the task queue are for asynchronous events that have completed. The callback function for setTimeout2 is executed, and console.log(4) prints 4.

    Event Table Macro task queue Microtask queue
    setTimeout1(1000ms) setTimeout3(0ms) empty
    empty empty empty
  • Let’s move on to the callback function for the next macro task, setTimeout3. Console. log(7) Print 7. The new Promise is then encountered, and the synchronization code console.log(8) is executed to print 8. Put the callback function registered in the THEN into the microtask queue.

    Event Table Macro task queue Microtask queue
    setTimeout1(1000ms) empty then
  • After the macro task is completed, the microtask is executed. Run console.log(9) and print 9.

  • If the time has not reached 1000ms, you need to wait until the time is complete and put setTimeout1 into the macro task queue.

    Event Table Macro task queue Microtask queue
    empty setTimeout1(1000ms) empty
  • Finally execute the macro task setTimeout1, console.log(2) execute, print 2.

    At this point, the code is all executed.

The actual sample

  1. Subject to a
const myPromise = () => Promise.resolve('1')
const myPromise2 = () => Promise.resolve('2')

function firstFunction() {
  myPromise().then(res => console.log(res))
  console.log('3')
}

async function secondFunction() {
  console.log(await myPromise2())
  console.log('4')
}

firstFunction()
secondFunction()
Copy the code
  1. Topic 2
function test() {
  setTimeout(() => {
    Promise.resolve(4).then(res => { console.log(res); })
    Promise.resolve(7).then(res => { console.log(res); })
    setTimeout(() => {
      console.log(3);
    })
  })
  setTimeout(() => {
    Promise.resolve(9).then(res => { console.log(res); })
  })
  Promise.resolve(1).then(res => { console.log(res); })
  Promise.resolve(2).then(res => { console.log(res); })
}
test()
Copy the code
  1. The title three
function test() {
  setTimeout(() => {
    Promise.resolve(4).then(res => { console.log(res); })
    Promise.resolve(7).then(res => { console.log(res); })
    setTimeout(() => {
      console.log(3);
    }, 400)
  }, 400)
  setTimeout(() => {
    Promise.resolve(9).then(res => { console.log(res); })
  })
  Promise.resolve(1).then(res => { console.log(res); })
  Promise.resolve(2).then(res => { console.log(res); })
}
test()
Copy the code

Refer to the article

  1. Do you really understand Promise?
  2. JS event loop mechanism (Event loop) macro task/micro task
  3. Microtask, macro task, and event-loop