前段时间看到了一道面试题,关于async/await、promise和setTimeout的执行顺序,觉得挺有意思的,记录一下,加深理解。
题目:写出这段程序的打印顺序。并解释一下原理。
async function async1() {
console.log('async1 start');
await async2();
console.log('asnyc1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeOut');
}, 0);
async1();
new Promise(function (reslove) {
console.log('promise1');
reslove();
}).then(function () {
console.log('promise2');
})
console.log('script end');
解题思路:
做这道题之前我们首先要知道一个概念,就是js代码的执行顺序:
JavaScript 代码在运行时会被分为两种任务类型:宏任务和微任务。
宏任务包括整体代码 script,setTimeout,setInterval,setImmediate 和 I/O 操作等。而微任务则包括 process.nextTick,Promise,async/await 和 MutationObserver 等。
JavaScript 的执行顺序遵循一个事件循环机制,在每一次循环中,会执行一个宏任务,并执行它产生的所有微任务,然后进入下一次循环。一般来说,一个宏任务执行过程中,会产生若干个微任务,它们会被加入任务队列,等待当前宏任务执行完毕后,执行微任务队列中的所有任务。
具体地,JavaScript 代码的执行顺序如下:
执行全局代码,初始化全局环境。
从宏任务(macrotask)队列中取出一个最先进入队列的任务,执行它。
在执行过程中,产生的微任务(microtask)会被加入到微任务队列中。
等待当前宏任务和所有微任务都执行完毕,进入下一轮事件循环,取出一个最先进入任务队列的宏任务,执行它,如果此时宏任务队列中没有任务,则等待新的宏任务加入队列。
重复上述步骤,直到程序结束。
需要注意的是,JavaScript 代码执行期间,只有一个宏任务在执行,也只有一个微任务队列。因此,如果当前宏任务还没有执行完毕,新的宏任务是不会被执行的,直到当前宏任务执行完毕后,再执行下一个宏任务。同时,当当前宏任务执行完毕后,先处理微任务队列中的所有任务,然后再进入下一个宏任务的执行。
了解了什么是宏任务和微任务,就好理解多了,简单解释一下就是,在js执行过程中,首先执行
宏任务 => 微任务的Event Queue => 宏任务的Event Queue。
理解了执行顺序:
js在对 setTimeOut的处理方式
setTimeOut并不是直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。setTimeOut执行需要满足两个条件:
1. 主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回掉函数
2. 这个回掉函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
也就是说当定时器的时间到了之后,会将内部函数放入宏任务的Event Queue,并不会立即执行。
promise、async/await
首先,new Promise是同步的任务,会被放到主进程中去立即执行。而.then()函数是异步任务会放到异步队列中去,那什么时候放到异步队列中去呢?当你的promise状态结束的时候,就会立即放进异步队列中去了。
带async关键字的函数会返回一个promise对象,如果里面没有await,执行起来等同于普通函数;如果没有await,async函数并没有很厉害是不是。
await 关键字要在 async 关键字函数的内部,await 写在外面会报错;await如同他的语意,就是在等待,等待右侧的表达式完成。此时的await会让出线程,阻塞async内后续的代码,先去执行async外的代码。等外面的同步代码执行完毕,才会执行里面的后续代码。就算await的不是promise对象,是一个同步函数,也会等这样操作。
根据上述我们将题目进行一下解析

执行顺序:宏任务 => 微任务的Event Queue => 宏任务的Event Queue。
根据图片显示我们来整理一下流程:
首先,打印 "script start",表示脚本开始执行。
继续执行,调用
async1()函数。在
async1()函数中,打印 "async1 start"。调用
async2()函数。在
async2()函数中,打印 "async2"。回到
async1()函数,由于await async2()是一个异步操作,所以async1()函数会暂时挂起,等待async2()函数执行完成。接着,执行
new Promise()的构造函数,打印 "promise1"。执行
reslove(),Promise 状态变为 resolved。调用
then()方法注册回调函数,在 Promise resolve 后,打印 "promise2"。继续执行,打印 "script end"。
回到
async1()函数,await async2()操作完成,继续执行后续代码。在
async1()函数中,打印 "asnyc1 end"。最后,执行
setTimeout(),由于设置了 0ms 的延迟,所以会尽快执行,打印 "setTimeOut"。
打印结果:
script start
async1 start
async2
promise1
script end
asnyc1 end
promise2
setTimeOut
参考:(18条消息) 关于async/await、promise和setTimeout执行顺序_async promise settimeout执行顺序_yun_hou的博客-CSDN博客
文章通过一道面试题探讨了JavaScript中async/await、promise和setTimeout的执行顺序,解释了宏任务和微任务的概念以及事件循环机制。在给定的代码示例中,首先执行全局脚本,然后是async1()函数,遇到await时暂停并执行promise,接着是scriptend,最后是setTimeout回调和promise的then回调。

547

被折叠的 条评论
为什么被折叠?



