028-86261949

当前位置:首页 > 技术交流 > 帮你一步步看清async/await和promise的执行顺序

帮你一步步看清async/await和promise的执行顺序

2019/01/03 09:44 分类: 技术交流 浏览:0

主要内容:
  • 对于async await的理解
  • 画图一步步看清宏任务、微任务的执行过程
接下来我们测试一下,看看自己有没有必要接着往下看:
这是去年的一条面试题,你是否能够正确说出打印顺序以及执行这一步骤的原因呢?
<script>
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>
 
注:因为这是一道前端面试题,所以答案是以浏览器的机制为准,在node平台上运行会有差异。
答案:script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout
如果你的运行结果和答案的一直,可以选择跳过这篇文章啦!
对于async await的理解
这部分,主要会讲解3点内容
  • async做了一件什么事?
  • await在等什么?
  • await等到之后,做了一件什么事?
  • 补充:async、await和promise有哪些优势?
  • async做了一件什么事?
一句话概括 : 带async关键字的函数,它使得你函数的返回值必定是promise对象
也就是,如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装 ;如果async关键字函数显式地返回promise,那就以你返回的promise为准,一下是一个简单的例子,可以看到async关键字函数和普通函数返回值的区别:
<script>
async function fn1(){
return 123
}
function fn2(){
return 123
}
console.log(fn1())
console.log(fn2())
</script>
所以你看,async函数也没啥了不起的,有看到带有async关键字的函数也不用紧张,你就想它无非就是把return值包装了一下,其他就跟普通函数一样。
关于async关键字还有哪些需要注意的?
  • 在语义上要理解,async表示函数内部有异步操作
  • · 另外注意,一般await关键字要在async关键字函数的内部,await写在外面会报错。
2. await在等什么?
一句概括:await等的是右侧表达式的结果,也就是说,右侧如果是函数,name函数的return值就是---表达式的结果;右侧如果是一个'hello'或者什么值,那表达式的结果就是'hello'
<script>
async function async1(){
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2(){
console.log('async2')
}
async1();
console.log('script start');
</script>
这里要注意一点,可能大家都知道await会让出线程,阻塞后面的代码,name上面例子中,'async2'和'script start'谁会先打印呢?是从左向右执行,一旦碰到await直接跳出,阻塞async2()的执行?还是从右向左,先执行async2后,发现await关键字,于是让出线程,阻塞代码呢?
实践的结论是,从右向左。先打印async2,后打印script start
3. await等到之后,做了一件什么事情?
那么右侧表达式的结果,就是await要等的东西。
等到之后,对于await来说,分2个情况
  • 不是promise对象
  • 是promise对象
如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西作为await表达式的结果。
如果它等到的是一个promise对象,await也会暂停async后面的代码,先执行async外面的同步代码,等着Promise对象fulfilled,然后把resolve的参数作为await表达式的运行结果。
2>画图一步步看清宏任务、微任务的执行过程
我们以考片的经典面试题为例,分析这个例子中的宏任务和微任务。
什么是宏任务和微任务?W3C规定:
...3. Run: Run the selected task
....6. Microtasks: Perform a microtask checkpoint.
7. Update the rendering.
一次时间循环包括:执行tasks,检查Microtasks队列并执行,执行UI渲染(如果需要)
  • tasks任务包括:函数、加入队列的事件回调。
  • Microtasks任务包括:Promise.then、MutationObserver回调、process.nextTick
也就是说,一段代码会被分为两部分,tasks部分和Microtasks部分,执行完后,对该段代码内的UI变更进行处理(不包含内部为了执行代码立即进行的重绘),很明显Microtasks就是为了实现异步操作而设计的。
好了,言归正传,继续看我们之前的题:
<script>
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>
一段代码执行时,会先执行宏观任务中的同步代码,如果执行中遇到setTimeout之类宏任务,那么就把这个setTimeout内部的函数推入【宏任务的队列】中,下一轮宏任务执行时调用。
如果在执行中遇到promise.then( )之类的微任务,就会推入到【当前宏任务的微任务队列中,在本轮宏任务的同步代码执行都完成后,依次执行所有的任务1、2、3】

 
首先,直接打印同步代码 console.log('script start')

 
将setTimeout放入宏任务队列

 
此时我们开始启动async1函数,函数带有async关键字,但是它只是把return值包装成了promise,其他跟普通函数没有什么区别,按照函数内部分执行顺序,我们会先打印console.log('async1 start')

 
接下来我们遇到的就是await async2( ),前文提到await,它先计算出右侧结果,并暂时中断async函数。因此目前就直接打印console.log('async2')

 
被阻塞后,要执行async之外的代码,执行到new Promise(),Promise构造函数是直接调用的同步代码,所以此处console.log('promise1')

 
代码接着运行到promise.then( ),发现这是一个微任务,所以暂时不打印,只是推入当前宏任务的微任务队列中。
注意:这里只是把promise.then( )推入微任务队列中,并没有执行。微任务会在当前宏任务的同步代码执行完毕后,才会依次执行

 
紧接着打印同步代码console.log('script end'),此刻async外的代码终于走结束了,就该回到await表达式那里,执行await Promise.resolve(undefined)了

 
回到async内部,执行await Promise.resolve(undefined),这部分可能不太好理解,如果一个Promise被传递给一个await 操作符,await将等待Promise正常处理完成后并返回其处理结果。此处的await Promise.resolve( )就类似于
Promise.resolve(undefined).then(undefined) => { }
把then执行完,才是await async2()执行结束,await async2()执行结束,才能继续执行后面的代码

 
此时当前的宏任务1已执行完毕,要处理微任务队列中的代码,微任务队列,也要遵循先进先出的原则:
  • 执行微任务1,打印promise1
  • 执行微任务2,没什么内容
在执行微任务2后,await async2()的语句就彻底结束了,后面的代码不会再阻塞,所以打印console.log('async1 end'),宏观任务1执行完之后,随之执行宏任务2,最后打印了console.log('setTimeout')
不知道经过这么详细的解释之后,你是否看懂了async await和promise的执行顺序呢?提醒小伙伴哦,部分浏览器打印出来的顺序可能会存在一些小的差异性,小伙伴要主动研究查找原因哦!
#标签:前端,web