An essay and an interview question

Recently, an article called “8 Pictures to Help You See the Order of Async /await and Promise Execution step by step” caught my attention.

The author uses an introduction from the 2017 Toutiao front interview to explain why the final results were implemented step by step. It involves many concepts, such as asynchronous execution sequence, macro task, micro task and so on. At the same time, the author defines the execution scope, based on the event loop mechanism of the browser. Here is the code for the original problem:

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

Next, the author gives the answer first. Readers are expected to test themselves first.

script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
Copy the code

When I looked at this problem, I first wrote down the result according to my own understanding.

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
Copy the code

Some important concepts

Here we need to talk briefly about the concept of event loop.

  • Javascript is single-threaded, and all synchronization tasks are performed in the main thread.
  • In addition to the main thread, there is a task queue. Each time an asynchronous task has a result, an event is added to the task queue.
  • When all the tasks in the main thread are completed, the system reads the events in the task queue in sequence. The corresponding asynchronous task enters the main thread and begins execution.
  • There will be differences between asynchronous tasks, so their execution priority will also be different. It can be roughly divided into micro tasks (such as Promise, MutaionObserver, etc.) and macro tasks (such as setTimeout, setInterval, I/O, etc.). Microtasks are always executed before macro tasks in the same event loop.
  • The main thread repeats the above steps until all tasks are completed.

Also, there is the concept of async/await.

  • Async functions can be understood as syntactic sugar for Generator functions.
  • It builds on promises and is always used with await.
  • Await returns a Promise object, or the value of an expression.
  • The goal is to make asynchronous operations more elegant and write like synchronous operations.

I understand it

Let me tell you more about my understanding of this problem.

  • First, based on the number of consoles, eight lines of results are output.
  • I glance at the code again, see setTimeout, and silently fill it in at line 8.
  • Near setTimeout, you see console.log(‘script start’) and async1(), confirming that they are synchronous tasks that will be executed first in the main thread. So, no problem with script start on line 1 and async1 start on line 2.
  • Next, I encountered await. Literally, let’s wait. Wait for the async2() function to return and block subsequent code. So, line 3 is async2.
  • Logically, we are done with await, it’s time for console.log(‘async1 end’) output. However, remember that there is a Promise below, and note that when a Promise is new, the code in the resolve method executes immediately. If async1() is not preceded by await, promise1 is queued higher. So now in line 4, insert promise1.
  • Next, the synchronization task console.log(‘script end’) is executed. Line 5 fills script end.
  • There are lines 6 and 7 left unfilled. To recall the concept of async/await mentioned above, the purpose is for asynchrony to be written like synchronization. So, I think console.log(‘async1 end’) is a synchronization task. So line 6 fills async1 end.
  • Finally, insert promise2 in line 7 as a logical step.

Different from the author’s answer

Looking back and comparing the author’s answers, I found that the order of lines 6 and 7 was wrong.

Read the article a few times over and over again how async1 end and promise2 come first, and still can’t understand why Async1 end comes first in Chrome.

Then, looking in the comments section, I saw someone asking the same question. Rhinel proposes that in his 72.0.3622.0 (official) Dev (64-bit) Chrome, the result of running is async1 end before promise2.

Then IT occurred to me that the JS specification might change in the future. I tried it with my React project (babel-Loader version 7.1.5). .babelrc presets stage-3). The current version, chromeV71, does have a problem with the execution order here.

So I left a comment for the author to discuss. Rhinel also confirmed that the order improvements were recently released. This article called Faster Async Functions and Promises explains the improvements and how they work. Not long after, the author added the results of our discussion for readers’ reference at the end of his article.

conclusion

Finally, I’d like to say that this article, although derived from an interview question, is a process of thinking, discussing, and verifying the browser execution order. But it is these processes that allow more ideas to collide, concepts to be further understood, and norms to be clarified.

If there is an opportunity, I hope I can have more communication with my friends.

PS: Welcome to follow my public account “Chao Ge Front-end Small stack” to exchange more ideas and technologies.