====据说这是今日头条去年的一道笔试题,主要考察的是setTimeout async promise执行顺序
~先双手奉上这道题目~
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');
-
首先,我们先来了解几个概念:
JS众所周知是单线程语言,Javascript引擎同一时刻只能执行一个代码块,使用Event Loop作为它的异步执行机制
-
那么Event Loop是如何实现异步呢,个人浅显的理解如下:
- 同步代码按照上下文的顺序放进主进程中去执行
- 异步函数放进异步队列中,等待执行,在异步队列执行的顺序按照先进先出的原则
-
等主进程中的同步函数执行完毕后,轮询去执行异步队列中的异步函数
⚠️注意: setTimeOut并不是直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。setTimeOut执行需要满足两个条件:
1. 主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回掉函数 2. 这个回掉函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
-
理解了Eventloop异步实现的方式,再来补充一下promise、async/await
-
首先,new Promise是同步的任务,会被放到主进程中去立即执行。而.then()函数是异步任务会放到异步队列中去,那什么时候放到异步队列中去呢?当你的promise状态结束的时候,就会立即放进异步队列中去了。如果你要问他和setTimeOut谁当进去的快,要从下面两个方面考虑:
1. promise结束时。.then内函数插入异步队列的时间与setTimeOut的回掉函数插入队列的时间,谁的早,谁的就最快 2. **如果promise是同步的而setTimeOut时间是0,那么是promise先执行**。至于什么,查了很多的资料,了解到:一个浏览器环境只能有一个事件循环,而一个事件循环可以有多个任务队列。settimeout所在的队列与promise.then()的队列不同,面对此种情况,v8实现的时候会先从promise.then()的队列取任务,但是并没有很理解,如果有大佬愿意指点迷津,请留言告知?
- 带async关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数;如果没有await,async函数并没有很厉害是不是
- await 关键字要在 async 关键字函数的内部,await 写在外面会报错;await如同他的语意,就是在等待,等待右侧的表达式完成。此时的await会让出线程,阻塞async内后续的代码,先去执行async外的代码。等外面的同步代码执行完毕,才会执行里面的后续代码。就算await的不是promise对象,是一个同步函数,也会等这样操作
-
接下来,带着上面的那些总结,步入正题
分析上述的代码,给任务类型分类
async function async1() {
console.log( 'async1 start’ ) // 同步代码2
await async2() // 执行async2
console.log( 'async1 end’ ) // 需要等async1外面的代码执行完,并且async2也执行完才会执行
}
async function async2() {
console.log( 'async2’ ) // 同步代码3
}
// ===================== 从这里开始了表演,所以在这里走起 ===============================
console.log( 'script start’ ) // 同步代码1
setTimeout( function () {
// setTimeout放入event-loop中的macro-tasks队列,暂不执行
console.log( 'setTimeout' )
}, 0 )
async1() // 如果有await,第一个【await前面的代码】属于主进程执行—看最上面函数内分析
new Promise( function ( resolve ) { // 注意这个方法里面的是同步
// 同步代码4
console.log( 'promise1’ )
resolve();
} ).then(
// .then()放入event-loop中的micro-tasks队列
function () {
console.log( 'promise2' )
}
)
console.log( 'script end’ ) // 同步代码5
-
最先输出的是同步代码,按照上下文执行顺序是
'script start’ 'async1 start’ 'async2’ 'promise1’ 'script end’
- 接下来,给异步队列执行的排序
第一个执行的函数是async1() ,里面有await,他要等待,等待就是要让async外面的代码先执行,外面的那些同步代码执行好了之后,在执行这个async里面的,await后面的代码执行。所以遇到await就让出了线程,给到后面的代码,那么下面的
promise.then
会先执行,后执行await后面的代码
async1()的await后面和setTimeOut哪个先执行,这个要看await等待的是什么。此处等待的是async2函数,这个函数里面没有await等待,带async
关键字的函数返回的是一个promise对象,所以async1中等待的是一个promise对象,并且这个promise对象里面只有同步执行,会被放进时间循环的micro-tasks队列,该队列比setTimeOut的队列先执行,执行完了之后还不能执行setTimeOut,因为执行完了之后就不阻塞主进程了,主进程要接着执行;也就是await后面的同步代码,要先去执行主进程。所以setTimeOut是在最后执行的
再次验证了上面所说的setTimeOut执行的必要条件之一是主进程空闲了
因此,异步代码的执行顺序是
‘promise2' // async函数外的代码先执行
'async1 end’ // -- await不阻塞了,async后面的同步代码
'setTimeout' // promise.then的队列比setTimeout队列先执行
如果你真正的理解了Event Loop的执行机制,并且知道setTimeout的与promise.then并非一个队列里面的,那么这道题就是很简单的送分题
上述如有不足或者不当指出,请各位大佬匡正~~?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。