Synchronous mode Asynchronous mode

To understand event loops, we first need to understand the synchronous and asynchronous modes of JavaScript.

As we all know, the current mainstream javaScript environment is to execute the javaScript code in the mode of single thread. The reason why javaScript adopts single thread is related to its original design intention.

The earliest javaScript language is a scripting language that runs in the browser, and its purpose is to achieve dynamic interaction on the page.

Dom manipulation is at the heart of page interaction, so he must use a single-threaded model, or else complex thread synchronization will occur.

For example, if we have multiple threads working in javaScript at the same time, and one thread changes a DOM element and another thread removes it, our browser can’t tell which thread is working on it.

So to avoid this thread synchronization problem, javaScript was designed to work in single-threaded mode from the beginning, and this has been one of the core features of the language.

The single thread here means that in the JS execution environment, there is only one thread responsible for executing the code.

So you can imagine that there is only one person inside of us who is executing our code. There is only one person, and he can only perform one task at the same time, so if there are multiple tasks, they have to queue up and finish them one by one.

The biggest advantage of this mode is that it is safer and simpler, while the disadvantage is also obvious. If we encounter a particularly time-consuming task, the following tasks have to queue up and wait for the end of this task.

console.log('foo');

for (let i = 0; i < 100000; i++) {
    console.log('Time-consuming operation');
}

console.log('Wait for time-consuming operation to end');
Copy the code

That would lead to a delay in the execution of our entire program and a case of suspended animation.

To solve the problem of time-consuming tasks blocking execution, the javaScript language divides the execution modes of tasks into two types. They are Synchronous and Asynchronous.

Here we understand the JS execution is divided into synchronous tasks and asynchronous tasks. The loop example above is not accurate, as we usually refer to asynchronous tasks as Ajax requests or timers.

Event loop

There are two important concepts in the event loop called macro tasks and micro tasks. Both macro and micro tasks refer to asynchronous tasks.

We all know that JavaScript is executed from the top down, and there are two things involved in the execution stack and the task queue. Executing code is placed in the execution stack, and macro and micro tasks are placed in the task queue for execution.

For example, in the following code, JS is executed from top to bottom. First, variable name is declared and the value is assigned to YD, and then setTimeout timer is executed. Since setTimeout is an asynchronous task, the function in setTimeout will be delayed. The function in this timer is put into the task queue for 1s.

The code continues to execute, printing out the value of name. Since the asynchronous function has not been executed at this point, the printed value is still YD.

After 1s, when the timer mounted in the browser is ready to execute and starts firing, the setTimeout function in the task queue will be put into the execution stack to execute the name=’zd’ operation.

let name = 'yd';

setTimeout(function() {
    name = 'zd';
}, 1000);

console.log(name);
Copy the code

The above code enforcement mechanism is simpler, js top-down implementation first, when confronted with a task asynchronous tasks will be added to the task queue, wait until the current js, stack has been completed to check whether there is can be performed in the task queue task, if there is to remove the task from the queue into the execution stack.

Macro task

In order to enable the orderly execution of internal JS tasks and DOM tasks, browsers will render the page after the completion of the previous task and before the start of the next task.

task -> rander -> task
Copy the code

Macro tasks in browsers generally include:

  • setTimeout, setInterval

Timer we all know his role and usage, here is not an example.

  • MessageChannel

Message channel, not very good compatibility, example below.

const channel = new MessageChannel();
// Add a message to the port number and send a message
channel.port1.postMessage('I love you');
// Register to receive event, after binding to receive function, still can receive, so it can be seen that the asynchronous execution
channel.post2.onmessage = function(e) {
    console.log(e.data);
};
console.log('hello'); // I love you.
Copy the code
  • postMessage

Message communication mechanism, also can not be described much.

  • setImmediate

The timer is executed immediately without setting the time. It is implemented only in Internet Explorer.

setImmediate(function() {
    console.log('Start timer now, time cannot be set')})Copy the code

These are common macro tasks, but macro tasks also include click events and other mechanisms.

Micro tasks

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

The microtask queue executes as soon as no other JS code is executing in the execution stack or each macro task completes.

If a new microtask is added to the microtask queue during the execution of the microtask, the new microtask is added to the end of the queue and will be executed later.

Microtasks include:

  • promise.then,

Promise’s then method is a microtask.

  • Async await.

The content after await of async functions is also executed in the form of microtasks.

  • MutationObserver

The purpose of MutationObserver is to monitor changes in the DOM, which are executed when the DOM changes. The time node is to wait until all code has been executed

const observer = new MutationObserver(() = > {
    console.log('Node updated');
    console.log(document.getElementById('app').children.length);
});
observer.observe(document.getElementById('app'), {
    'childList': true});for (let i = 0; i < 20; i++) {
    document.getElementById('app').appendChild(document.createElement('p'));
}
for (let i = 0; i < 20; i++) {
    document.getElementById('app').appendChild(document.createElement('span'));
}
Copy the code

EventLoop

We can see the event loop through the following code execution order.


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

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

console.log('start');
Copy the code

SetTimeout will be executed immediately, but its execution result will generate an asynchronous macro task, which will be put into the macro task queue and executed after a certain period of time. The value set here is 0 seconds, but 0 seconds will not be executed immediately. Because the task queue must wait for the current stack to complete before it is considered to execute.

The code then executes to promise.resolve ().then. In this case, the code is not task code and will be executed immediately, but promise.then will generate a microtask and put it in the microtask queue until the main stack completes execution.

The code continues down to console.log(‘start’), prints start, and the stack completes execution.

At this point we know there is console.log(‘timeout’) in the macro task queue; Log (‘then’) in the microtask queue; It’s time to execute. Which one of them will be executed first?

The JavaScript execution mechanism is very simple. After the main stack execution is completed, the microtask queue will be executed, and the first microtask will be executed first. After all the microtasks are executed, that is, after the microtask queue is emptied, the macro task queue will be checked. Execute the macro task that needs to be executed.

So it prints then, and then it prints timeout.

The summary is: perform synchronization code first, then perform microtask, and then check whether the macro task has reached the time, and then execute.

We know that the microtask queue will be emptied after the execution of the main execution stack, that is, all the microtasks will be executed. How will the microtask be executed when multiple macro tasks reach execution? Take the following code for example.

SetTimeout first creates a macro task, which in turn creates a promise.resolve ().then microtask. Then a macro task is created following promise.resolve ().then. Let’s take a look at the print order of this paragraph.

setTimeout(() = > {
    console.log('timeout1');
    Promise.resolve().then(data= > {
        console.log('then1');
    });
}, 0);

Promise.resolve().then(data= > {
    console.log('then2');
    setTimeout(() = > {
        console.log('timeout2');
    }, 0);
});
Copy the code

First, after the setTimeout execution, a macro task is created and put into the macro task queue. The task does not execute, so the internal Promise does not execute, and the code continues downwards.

The Promise that follows creates a microtask and places it in the microtask queue.

// setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(data= > {
        console.log('then1');
    });
// }, 0);

// Promise.resolve().then(data => {
    console.log('then2');
    setTimeout(() = > {
        console.log('timeout2');
    }, 0);
// });
Copy the code

At this point, there is a macro task in the macro task queue and a micro task in the micro task queue, and the two tasks are ready to execute. As mentioned earlier, the main execution stack will empty the microtasks first, so the microtasks will be executed in the execution stack. Then2 is printed and setTimeout is executed to generate a new macro task that is added to the macro task queue. The microtask is complete.

At this time, there are two tasks in the macro task queue. Since the timer time is 0, both of them are ready to execute. The queue mechanism is first join first execute, so the first join task (setTimeout above) will be executed on the execution stack, the timeout1 will be printed, and then a Promise. Then microtask will be created.

There is a console.log(‘timeout2’) in the macro task queue; Task, the microtask queue has a console.log(‘then1’); Task.

As you know from previous experience, the execution stack empifies the microtask queue after it finishes executing, so instead of continuing with the second macro task, the microtask queue is emptied again. Print then1. After the execution of the micro task is complete, go to the macro task to take out the macro task to be executed in the execution stack, print timeout2.

So the print order of the above code is then2 -> timeout1 -> then1 -> timeout2

The order of execution of the event loop is also relatively simple. First, the JavaScript code executes from top to bottom, putting the task in the macro task queue every time it encounters a macro task such as timer, and putting the task in the microtask queue when it encounters a microtask such as promise. then. Wait until the execution stack, the code has been completed clears the task queue, after the first to join the first execution after joining the execution, and then to check the macro task queue, the macro will fulfill to perform the stack, only take out a macro task, complete again empty task queue, empty out to check the macro task queue, and so on.

Event cycle interview questions

  1. How to print the following code – simple
const p = new Promise(function(resolve, reject){
    reject();
    resolve();
});
p.then(function() {
    console.log('success');
}, function() {
    console.log('failure');
});

/ / fail
Copy the code

Print only fails because the Promise state changes once.

  1. How to print the following code – getting started
const promise = new Promise((resolve, reject) = > {
    console.log(1);
    resolve();
    console.log(2);
});
promise.then(() = > {
    console.log(3);
});

// 1, 2, 3
Copy the code

The function passed in by New Promise is synchronous code that will be executed immediately, so it prints 1 and 2. Promise. Then is the microtask, and when the code terminates on its own, it clears the microtask queue and prints 3.

  1. How does the following code print – advanced
Promise.resolve(1)
.then(res= > 2)
.catch(err= > 3)
.then(res= > console.log(res));

/ / 2
Copy the code

The successful THEN returns a 2. The successful THEN returns a later THEN instead of a catch, and the later THEN prints out the previously returned 2.

  1. How does the following code execute – complex
Promise.resolve(1)
.then((x) = > x + 1)
.then(x= > { throw new Error('My Error')})
.catch(() = > 1)
.then(x= > x + 1)
.then(x= > console.log(x))
.catch(console.error);

/ / 2
Copy the code

First, if an exception is thrown in the THEN, the catch will be entered. If there is a normal return value in the catch, the catch will enter the later THEN. Then, x+1 (2) will be returned in the then, and the next THEN will be entered.

  1. How does the following code print – in depth

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

new Promise(function(resolve) {
    console.log(2);
    for (var i = 0; i < 10; i++) {
        i == 9 && resolve();
    }
    console.log(3);
}).then(function() {
    console.log(4);
});

console.log(5); 

// 2, 3, 5, 4, 1
Copy the code

The setTimeout in the first line creates a macro task and places it in the macro task queue. The function in new Promise is to synchronize the code to be executed immediately, printing 2 and 3, and changing the state of the Promise (meaning that the corresponding microtask can be executed immediately after the execution stack ends).

Promise.then creates the microtask and places it in the microtask queue.

The last line prints the number 5, and the stack completes. And then we empty the microtask queue, and the microtask queue prints the number 4, and when the microtask is done, the macro task starts executing and prints the number 1, so it prints 2, 3, 5, 4, 1.

  1. What is the print result of the following code

async function async1() {
    console.log('async1 start');
    await async2();
};

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('promise2');
});

console.log('script end2');

// script start async1 start async2 promise1 script end2 promise2 setTimeout
Copy the code

First of all, the first two pieces of code create the function. Creating the function does not mean that the function is executed. It has to wait for the call to be executed.

Then the console. The log (‘ script start); Scripy start is printed, so script start is printed first.

SetTimeout then creates a macro task and puts setTimeout into the macro task queue at a time of 0, which means it can be executed immediately.

Then call async1, which prints async1 start, and await async2 which is the function passed in by New Promise, which also prints async2 directly.

Next, the New Promise directly executes the code in function to print promise1 and the Promise state changes to resolve.

Promise.then creates a microtask promise2 into the microtask queue.

The last line directly prints Script end2. When the stack ends, the microtask queue is emptied, promisE2 is printed, and setTimeout is printed.

Async and await are syntactic candy of Promise. Promise. Then will create a micro-task, so when will async create a micro-task?

The await function in the async function is equivalent to the function passed in when the Promise is instantiated and will be executed immediately. The code below the await line is put into the microtask queue as a microtask.

We know that promise. then does not enter the execution sequence until the Promise function executes resolve or reject. Similarly, code following await will wait until the await function executes before entering the execution sequence. Grammatical sugar is written differently, but the principle is the same.

So let’s just rewrite this problem a little bit to understand it better.

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')}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('promise2')})Copy the code

The first two functions also create two functions, and console.log(‘script start’) prints script start when executed.

SetTimeout then creates a macro task that can be executed.

Async1 () calls the function async1, first prints async1 start, then await async2(), which is equivalent to the function passed in by new Promise, will be executed immediately, so prints async2.

The code console.log(‘async1 end’) below await will be put into the microtask queue as a microtask. Since async2 has finished executing, this microtask is also a microtask that can be executed.

So the async1 function completes and continues, the new Promise prints promise1 and changes the Promise state to create a microtask that can be executed again.

At this point, there is a setTimeout task in the macro task queue and two async1 end and promise2 microtasks in the microtask queue.

To clear the microtask queue, print async1 end and then promisE2 according to the first-in-first-out rule. Finally, execute the macro task queue and print setTimeout.

Therefore, the output is script start -> async1 start -> async2 -> promise1 -> AsynC1 end -> promise2 -> setTimeout


Like it or ask for it, in case you give it