We know that JS is executed in a single thread, so how does asynchronous code JS handle? For example, the following code would output:

console.log(1); setTimeout(function() { console.log(2); }, 0); new Promise(function(resolve) { console.log(3); resolve(Date.now()); }).then(function() { console.log(4); }); console.log(5); setTimeout(function() { new Promise(function(resolve) { console.log(6); resolve(Date.now()); }).then(function() { console.log(7); }); }, 0); Copy the codeCopy the code

In the case of not running we can guess the final output and then expand on what we are going to say.

1. Macro and micro tasks

Based on our years of experience writing Ajax, JS should be executed in the order of statements, making an asynchronous request and then executing it when the asynchronous result is returned. But how does he manage these operations internally?

In JS, tasks are divided into macroTask and microtask. These two tasks maintain a queue respectively and are executed using a first-in, first-out strategy! All synchronized tasks are executed on macro tasks.

Macro tasks include: Script (overall code), setTimeout, setInterval, I/O, UI interaction events, postMessage, MessageChannel, setImmediate(node.js environment).

Microtasks mainly include promise. then, MutationObserver, and Process.nexttick (node.js environment).

The specific operation steps are as follows:

  1. Retrieves a task from the head of a macro task to execute;
  2. Add microtasks to the queue if they are encountered during execution.
  3. After the execution of the macro task, whether there are tasks in the queue of the micro task, if there are tasks, execute them one by one until the execution is completed;
  4. GUI rendering;
  5. Go back to Step 1 until the macro task completes;

These four steps form an eventloop, called an eventloop.

Back to the code we talked about above:

console.log(1); setTimeout(function() { console.log(2); }, 0); new Promise(function(resolve) { console.log(3); resolve(Date.now()); }).then(function() { console.log(4); }); console.log(5); setTimeout(function() { new Promise(function(resolve) { console.log(6); resolve(Date.now()); }).then(function() { console.log(7); }); }, 0); Copy the codeCopy the code

Perform the following steps:

  1. Execute log(1) to print 1;
  2. When setTimeout is encountered, the code log(2) for the callback is added to the macro task for execution;
  3. Execute console.log(3) to add log(4) from then to the microtask;
  4. Execute log(5) to output 5;
  5. When setTimeout is encountered, add the code log(6, 7) for the callback to the macro task;
  6. After one task of the macro task is executed, check whether there is a task in the microtask queue. There is a microtask log(4) (added in Step 3), and execute output 4.
  7. Fetch the next macro task log(2) execution, output 2;
  8. After one task of a macro task is executed, check whether there is a task in the microtask queue.
  9. Fetch the next macro task execution, execute log(6), add the log(7) in then to the microtask;
  10. After the macro task is executed, there is a microtask log(7) (added in Step 9), execute output 7.

Therefore, the final output sequence is: 1, 3, 5, 4, 2, 6, 7;

We implement a slightly more time-consuming operation in promise.then, and this step becomes even more obvious:

console.log(1); var start = Date.now(); setTimeout(function() { console.log(2); }, 0); setTimeout(function() { console.log(4, Date.now() - start); }, 400); Promise.resolve().then(function() { var sum = function(a, b) { return Number(a) + Number(b); } var res = []; for(var i=0; i<5000000; i++) { var a = Math.floor(Math.random()*100); var b = Math.floor(Math.random()*200); res.push(sum(a, b)); } res = res.sort(); console.log(3); }) copy the codeCopy the code

Promise.then, mister forms an array of 5 million random numbers and sorts the array. Run this code and you’ll see that it prints 1 immediately, 3 a little later, and then 2. No matter how long you wait to print 3, 2 will always print after 3. This confirms that step 3 in eventLoop must wait for all microtasks to complete before starting the next macro task.

In the meantime, the output of this code is interesting:

setTimeout(function() { console.log(4, Date.now() - start); // 4, 1380 Different computer state, output time difference is not the same}, 400); Copy the codeCopy the code

Originally, the output was set to be 400ms later, but due to the serious time consuming of the previous task, the subsequent task could only be postponed to the later one. It also shows that the delay of setTimeout and setInterval operations is not accurate. These two methods can only be used for macro tasks after 400ms, but the specific execution time depends on whether the thread is free. If there are time-consuming operations in the previous task, or an infinite number of microtasks are added, the next task will be blocked.

2. async-await

We can also see from the above code that the promise. then code is a microservice, so how to execute the async-await code? For example:

function A() { return Promise.resolve(Date.now()); } async function B() { console.log(Math.random()); let now = await A(); console.log(now); } console.log(1); B(); console.log(2); Copy the codeCopy the code

In fact, async-await is just a syntactic candy for Promise+ Generator. Let’s rewrite the above code to make it a little clearer:

function B() { console.log(Math.random()); A().then(function(now) { console.log(now); }) } console.log(1); B(); console.log(2); Copy the codeCopy the code

1, 0.4793526730678652(random number), 2, 1557830834679(timestamp);

3. requestAnimationFrame

RequestAnimationFrame is also a method that executes asynchronously, but it is neither a macro task nor a microtask. As defined in MDN:

Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation. This method takes as an argument a callback function that is executed before the browser’s next redraw

RequestAnimationFrame is executed before GUI rendering, but after microservice, but requestAnimationFrame does not necessarily have to be executed during the current frame. It is up to the browser to decide which frame to execute according to the current policy.

4. To summarize

The two most important things to remember are that JS is a single-threaded and eventloop loop.

Author: Tencent IVWEB Team link: juejin.cn/post/684490… The copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.