不妨大胆一点,反正没有谁能活着离开这世界
在前端的面试中,常常会考察async ,await,setTimeout,Promise函数执行的顺序。请各位看官,细听分说。以后不再迷茫
任务队列
- JS分为同步任务和异步任务
- 同步任务都在主线程上(其实js是单线程)执行,由上至下形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,来了异步任务有了运行结果,就在任务队列里放置一个事件
- 一旦执行栈里所有的同步任务执行完毕的时候(及时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,依次开始执行
宏任务
macrotask,可以理解为每次执行栈执行的代码就是一个宏任务。浏览器为了能够使得JS内部macrotask与DOM任务能够有序的执行,会在一个macrotask执行结束后,在一个macrotask执行开始前,对页面进行重新渲染
(macro)task->渲染->(macro)task->...
macrotask主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务
microtask,可以理解是在当前task执行结束后立即执行的任务。也就是说在当前任务之后,下一个任务之前,重新渲染之前。
所以microtask的响应速度比setTimeout(setTimeout是任务)更快,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染之前)
microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
运行机制
事件循环中,每进行一次循环操作称为tick
- 执行一个宏任务(执行栈中没有就从事件队列中获取)
- 执行过程中如果遇到微服务,就将它添加到微服务的队列
- 宏任务执行完毕后,立即执行当前微服务队列中的所有微服务(依次执行)
-
渲染完毕之后,JS线程继续接管,开始下一个宏任务
Promise和async中的立即执行
我们知道Promise中的异步体现在then和catch中,所以卸载Promise中的代码是被当做同步任务立即执行的。而在async/await中,在出现await之前,其中的代码也是立即执行的
await干了什么见不得人的事呢
await等待的是一个表达式,这个表达式的返回值可以是一个promise对象也可以是其他值。由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask
做对下面的题,你就出师了
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');
分析:
首先打印script start,然后遇到了宏任务setTimeout,宏任务会加入到宏任务的队列
这时候开始执行async1(),async1中await之间立即执行,打印async1 start,然后输出async2,await之后的加入微观任务
然后遇到promise,promise里面的立即执行。then分配到微观任务
最后打印script end
这时候开始清空微任务'async1 end',promise2
最最后在循环执行宏任务,只剩下setTimeout,打印setTimeout
答案如下:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
参考自:https://github.com/advanced-frontend/daily-interview-question/issues/7