Eventloop+Promise interview questions come in different versions — the “get it” version, the “Get it” version, the “Get it” version, the “Go” version and the “Go” version. Let’s say the kids fight until the last one, and they’re going to be invincible when they come across this kind of problem. Of course, if the interviewers come up with a sicker version, I lose.

Version 1: Handy version

The sequence of events in eventLoop, macro tasks and micro tasks.

Ridicule: this does not understand, did not save, go home to study afresh.

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

This version of the interviewer is very friendly, only ask you a concept of understanding, understand the marcotask (microtask), this question is to send points.

My answer: This is Eventloop’s question. After the main script finishes running, there are microtask queues and macro task queues. Microtasks are executed first, followed by macro tasks.

PS: Conceptual issues

Sometimes the version is macro task > micro task > macro task, here I need to clarify a concept to avoid confusion. There is the concept of main script, which is the code that starts executing (code has to start executing at some point, right? Where do queues of macro tasks and microtasks come from?), which is defined as macro tasks (I like to keep the main script concept separate from the two task queues), Then according to the main script generated in the microtask queue and macro task queue, respectively empty, this time is to empty the microtask queue, then to empty the macro task queue.

Version 2: Easy version

In this version, interviewers try to test their understanding of Promise by adding a little spice:

Promise executors and then executors

Ridicule: this is a pit, promise master skilled, this is the life of the small episode.

setTimeout(()=>{
   console.log(1) 
},0)
let a=new Promise((resolve)=>{
    console.log(2)
    resolve()
}).then(()=>{
   console.log(3) 
}).then(()=>{
   console.log(4) 
})
console.log(5) 
Copy the code

What does Eventloop mean to you? Promise’s THEN is a microtask, you know, but how does that THEN work, and are Promise executors asynchronous or synchronous?

Error: Promise’s THEN is an asynchronous process. After each then is executed, a new loop is created, so the second THEN will be executed after setTimeout. (Yes, this is one of my answers. Please give me a gun, I really want to kill myself at that time.

The Promise executor is a synchronous function, that is, a non-asynchronous function that executes immediately, so it should execute with the current task. A Promise’s chain call to THEN generates a new Promise internally each time and executes then, pushing new functions into microtasks as it goes. Therefore, the next wave of macroTasks is not executed until the microtask queue is empty.

Detailed analysis

(If you don’t mind, I’ve written another article about implementing a Promise from zero, and the explanation is pretty straightforward.) Using the promise implementation in Babel’s core-JS as an example, let’s take a look at the promise implementation specification:

Code location: promise-polyfill

PromiseConstructor = function Promise(executor) {
    //...
    try {
      executor(bind(internalResolve, this, state), bind(internalReject, this, state)); } catch (err) { internalReject(this, state, err); }};Copy the code

It’s clear here that the Promise executor is a function that executes immediately.

then: function then(onFulfilled, onRejected) {
    var state = getInternalPromiseState(this);
    var reaction = newPromiseCapability(speciesConstructor(this, PromiseConstructor));
    reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true;
    reaction.fail = typeof onRejected == 'function' && onRejected;
    reaction.domain = IS_NODE ? process.domain : undefined;
    state.parent = true;
    state.reactions.push(reaction);
    if(state.state ! = PENDING) notify(this, state,false);
    return reaction.promise;
},
Copy the code

The then function for the Promise is followed by reaction. Promise, which returns a new Promise each time the THEN execution completes. That is, the current microtask queue is empty, but then it is added again, and the next macro task is not executed until the microtask queue is empty.

// State. Make eye contactthenVar chain = state. Reactions; microtask(function () {
    var value = state.value;
    var ok = state.state == FULFILLED;
    var i = 0;
    var run = function (reaction) {
        //...
    };
    while(chain.length > i) run(chain[i++]); / /... });Copy the code

Finally, after the Promise task resolve, then is executed. You can see that the then functions are executed in bulk, and the then callbacks are placed in a conspicuous function called microTask, indicating that the callbacks are executed in a microtask.

So how does the microtask queue work in browsers that don’t have Promises?

Little knowledge: Babel polyfills microtasks, using setImmediate if it has the platform, and custom leveraging various mediate functions such as Process. nextTick in NodeJS and postMessage in browsers. Or create a script to implement microtasks. Eventually, you use setTimeout, but that’s irrelevant to microtasks, and promise becomes part of the macro task.

Expand your thinking:

Why is it that sometimes the function in then is an array? Sometimes it’s just a function, right?

So let’s change that a little bit, and we’ll change the chaining function to the bottom one that calls then. Apart from the difference in usage between this and chain calls, the difference here is just practical. Chained calls generate a new Promise each time, meaning that each then callback belongs to a microTask, and these separate calls push the then callback functions into an array and execute them in batches. In other words, chained calls can be queued by other functions in Evenloop, while separate calls can’t. (In the most common case, there are no other asynchronous operations in THEN.) .

let a=new Promise((resolve)=>{
     console.log(2)
     resolve()
})
a.then(()=>{
    console.log(3) 
})
a.then(()=>{
    console.log(4) 
})
 
Copy the code

The “queue-jumping” behavior in microtasks will be explained in detail in the next module.

Version 3: Perfect version

This version is an evolution of the previous version of the promise’s then function, which did not return a promise. What if you created a promise within a promise’s THEN?

Then return a promise. Then return a promise

Teasing: Promise can be hell…

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then11")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then21")
    }).then(()=>{
        console.log("then23")
    })
}).then(()=>{
    console.log("then12")})Copy the code

According to the implementation process of the last microTask in the previous section, that is, all the then callback functions of a Promise are executed in a microTask function, but the execution of each callback function is divided into immediate execution according to the situation. Microtasks and macrotasks.

Don’t panic when you encounter nested promises. First, you need to have a queue in mind that you can place these functions in the corresponding queue.

Ready GO

The first round

  • Current Task: Promise1 is definitely a function that executes immediately. See executor in the previous chapter for immediate output[promise1]
  • Micro Task Queue: [first THEN of promise1]

The second round

  • Current task: then1 Executing, output immediatelythen11And the new Promise2promise2
  • Micro Task Queue: [new THEN function for promise2, and second THEN function for promise1]

In the third round

  • Current Task: Output of the then function of the new promise2then21And the second then function of promise1then12.
  • Micro Task Queue: [second THEN function of new promise2]

The fourth round of

  • Current Task: output of the second then function of the new promise2then23
  • micro task queue: []

END

Final result [promise1, then11 promise2, then21, then12, then23].

Version 1: What if then returns a Promise?

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then11")
    return new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then21")
    }).then(()=>{
        console.log("then23")
    })
}).then(()=>{
    console.log("then12")})Copy the code

This is the case where the then of a Promise returns a Promise. The focus of the test is on the Promise, not the Eventloop. It’s easy to understand why then12 executes after then23, where the second THEN of the Promise is essentially hanging on the return value of the last THEN of the new Promise.

Variant 2: If there is more than one Promise, does adding a new Promise affect the outcome?

new Promise((resolve,reject)=>{
    console.log("promise1")
    resolve()
}).then(()=>{
    console.log("then11")
    new Promise((resolve,reject)=>{
        console.log("promise2")
        resolve()
    }).then(()=>{
        console.log("then21")
    }).then(()=>{
        console.log("then23")
    })
}).then(()=>{
    console.log("then12")
})
new Promise((resolve,reject)=>{
    console.log("promise3")
    resolve()
}).then(()=>{
    console.log("then31")})Copy the code

We can also make a mental queue for this:

The first round

  • Current task: promise1, promise3
  • micro task queue: [The first THEN of promise1.The first THEN of promise3]

The second round

  • Current Task: then11, promise2, then31
  • micro task queue: [The first THEN of promise2.The second THEN of promise1]

In the third round

  • Current Task: then21, then12
  • micro task queue: [The second then of promise2]

The fourth round of

  • current task: then23
  • micro task queue: []

Final output: [promise1, promise3 then11, promise2, then31, then21, then12, then23]

Version 4: The ultimate version

Effects on Eventloop under async/await

Don’t get fooled by async/await.

I believe you have also seen this kind of topic, I have a fairly simple explanation, I do not know if you are interested.

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");
});
console.log('script end'); 
Copy the code

Async /await only affects execution within a function and does not affect execution order outside the function. Async1 () does not block subsequent program execution, await async2() equals a Promise, console.log(“async1 end”); The function that is executed after the then of the ahead Promise.

According to the chapter on the solution, the final output result: [async1 script start, start, async2 promise1, script end, async1 end, promise2, settimeout]

If you know the use of async/await, you won’t find it difficult, but if you don’t know or even know half of it, it’s a disaster.

  • The only issue at issue here is the priority of async’s THEN and promise’s THEN, as explained below. *

Priority explanation of async/await and promise

async function async1() {
    console.log("async1 start");
    await  async2();
    console.log("async1 end");
}
async  function async2() {
    console.log( 'async2'); } / / used totestTo see when an await actually executes a new promise (function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
}).then(function () {
    console.log("promise3");
}).then(function () {
    console.log("promise4");
}).then(function () {
    console.log("promise5");
});
Copy the code

Let me give you a question. If you polyfill async/await, how would you polyfill the above code? The author’s version is given below:

function promise1() {return new Promise((resolve)=>{
        console.log("async1 start");
        promise2().then(()=>{
            console.log("async1 end");
            resolve()
        })
    })
}
function promise2() {return new Promise((resolve)=>{
        console.log( 'async2'); 
        resolve() 
    })
}
Copy the code

In my opinion, async itself is a Promise, and await must follow a Promise, so create two new functions, each returning a Promise. Function promise1 waits for the completion of the Promise in function promise2.

According to the result of this version: [async1 start, async2 promise1, async1 end, promise2,… , async await before test promise.then, actually can get this result from the author’s polifill.

Then, to my surprise, with native async/await, I get a different result from polyfill! The results are: [async1 start, async2 promise1, promise2, promise3, async1 end,… Since promise. Then is a new round of microtask each time, async is output after 2 rounds of microtask (see version 3 for an explanation of THEN).

/* Sudden silence */

V8 and Nodejs12 have fixed this since async/await is considered too expensive because it takes 3 rounds of microtask to complete await. See pull on Github for details

Then, the author polyfills in another way. I believe that we have fully understood that “await” is a Promise, but what if the Promise is not a good Promise? Asynchrony is good asynchrony, Promise is bad Promise. V8 was brutal, adding two extra promises to fix this problem, simplifying the source code to look something like this:

// A description that is not quite accuratefunction promise1(){
    console.log("async1 start"); Resolve () const implicit_promise= promise.resolve () const implicit_promise= promise.resolve () In order to guarantee promise2 executor is the feeling of synchronous const promise = promise2 () / / https://tc39.github.io/ecma262/#sec-performpromisethen// Throwaway, for specification, Resolve () //console.log((d)=>{console.log(d)}))return implicit_promise.then(()=>{
        throwaway.then(()=>{
            promise.then(()=>{
                console.log('async1 end'); })})})}Copy the code

Ps: In order to forcibly postpone the execution of the two microtasks, the author also took great pains.

To sum up: async/await will sometimes delay two rounds of microtask and execute in the third round of microtask. The main reason is that the browser has to parse this method. In order to parse an await, two extra promises need to be created, so it is expensive. Later V8 eliminated a Promise and eliminated two rounds of microtasks in order to reduce costs, so now the latest version should be a “zero cost” asynchronous.

Version 5: Extreme abnormal version

It’s a big meal. It’s a big meal to think about. Can take an examination of this, can only say that the interviewer ruthless words are also many.

4, NodeJS events +Promise+async/await+ setImmediate

Bad point: I don’t know which one might come first

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");
});
async1()
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
}).then(function () {
    console.log("promise2");
});
setImmediate(()=>{
    console.log("setImmediate")
})
process.nextTick(()=>{
    console.log("process")
})
console.log('script end'); 
Copy the code

Queue start

The first round:

  • Current task: “script start”, “async1 start”, ‘async2’, “promise1”, “script end”
  • Micro Task Queue: [async.promise.then.process]
  • Macro Task Queue: [setTimeout.setImmediate]

The second round

  • Current task: Process, async1 end,promise.then
  • Micro Task Queue: []
  • Macro Task Queue: [setTimeout.setImmediate]

In the third round

  • The current task: setTimeout, setImmediate
  • Micro Task Queue: []
  • Macro task Queue: []

The end result: [async1 script start, start, async2 promise1, script end, process, async1 end, promise2, setTimeout, setImmediate]

Similarly, the priority between “async1 end” and “promise2” varies from platform to platform.

Author dry goods summary

When processing an evenloop execution sequence:

  • The first step is to identify macro tasks and micro tasks

    • Macro tasks: Script, setTimeout, setImmediate, Executor in promise
    • Microtasks: Promise.then, process.nexttick
  • Do not panic if async/await occurs, they are only allowed to bode in marked functions, and out of this function still follow the trend of the large army.

  • The third step is to make different decisions based on how the then is used in the Promise, whether it is chained or called separately.

  • The last step is to remember some special events

    • For instance,process.nextTickPriority overPromise.then

Refer to the website, recommended reading:

How to implement async/await, faster asynchronous functions and promises in V8

Ecma262 for the async/await specification

And the source code for Babel-Polyfill, Promise

Afterword.

Hello~Anybody here?

Originally, the author did not want to write this article, because there is a kind of 5 years of college entrance examination 3 years of simulation of both the sense of sight, but the interviewers are too ferocious, in order to “torture” the interview without its extreme, how abnormal how to come. However, I know how to use Eventloop completely, so it’s a blessing in disguise

Did anyone see the end? Come and talk to me about some of the kinky topics you’ve encountered in Eventloop+Promise.

Eventloop is not terrible, terrible is meet Promise