第 8 题:setTimeout、Promise、Async/Await 的区别
这题怎么没人答,我说下我粗浅的认识,抛砖引玉,欢迎指正和补充。
我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
2. 基础知识
需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!
js运行原理
首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址
这个是核心思想的截图
Microtask、Macrotask/Task
Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址。
看博客注意事项
博客内有带执行步骤的动画,一定要亲自点一下⭐
博客中将Macrotask叫做Task;
分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了
Microtask和Macrotask所包含的api:
如果不想看英文博客,我在这里作一点简单的总结:
先进先出
的原则,一次执行完所有Microtask队列任务;更多内容可以看我的github
几乎同样的疑问,,,我搞不懂为什么await下面的代码会在script end之后输出,,我以为是在async2执行完就会执行的 然后才执行 script end,,,,不太懂
你好,这里的例子是写错了吧,实际运行不是这样的结果呢
文章中的例子确实写错了
文章中的的结果确实写错了,现在已经更正.谢谢你的指正! 如果对你造成误导,非常抱歉!
第一次执行栈的同步任务都完成后,接着处理的应该是微任务吧,然后再从宏任务队列里拿一条宏任务到执行栈中,等执行栈中的宏任务处理完,再去清空微任务队列。
async函数本身返回是一个promise,为什么async2显式返回promise和不显式返回,两行打印顺序不一致,什么原理?显式返回顺序:Promise then --> async1 end;返回普通值顺序:async1 end --> Promise then
setTimeout 和promise 好理解, 宏任务队列和微任务队列。,
await fun() n balabala..
await返回一个promise 后执行 balabala 所以我的理解就像使用了.then(()=>balabala),也是进入的微任务队列。 这样执行的顺序也就明白了
finally也是返回一个promise,自然可以用catch捕获运行时错误。
return ‘async2’时,执行代码相当于如下:
new Promise(function (resolve) {
resolve('async2');
}).then(function() {
console.log('async1 end');
});
console.log('async1 end');排在microtask queue前面,所以输出顺序是async1 end --> Promise then
return Promise.resolve('async2')时,执行代码相当于如下:
new Promise(function (resolve) {
resolve(Promise.resolve('async2'));
}).then(function() {
console.log('async1 end');
});
此时的then取决于里层promise的状态,console.log('async1 end')会排在microtask queue后面,所以输出顺序是Promise then --> async1 end
自己回答一波,若有错误请各位指出,互相学习!谢谢 ~
鉴于上面各位大佬回答的,其实这题主要还是考浏览器的EVENT LOOP
setTimeout属于宏任务,Promise属于微任务.而async和await其实是geneorator的语法糖,实质上最后返回的也是promise,所以我将其归为微任务(有错的,请指出,谢谢)。
而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。而Task队列中,我们又再之前提及到分:微任务和宏任务
微任务的优先级大于宏任务,所以在执行栈为空的时候,首先会去执行Micortask(微任务)队列,执行完毕后再去取Macrotask(宏任务)队列去执行栈中执行,一次执行一个,再去检查Micortask(微任务),若存在则执行Microtask,若没有则取下一个Macrotask任务继续执行,直至为空。
以上,是我对EventLoop的回答,但是我还是想在此基础上回答下关于这三个的异步不同之处。
setTimeout
setTimeout的异步使用方法算是比较古老的回调函数方式,就是我们之前写Jquery的时候,ajax的最常见的使用方式,这种的好处在于用很简单的方式实现了异步的方式,从而解决了异步直肠子的问题(耗时任务,一直处于等待)。缺点:回调地狱,这是写了多年Jq的一直很恶心的地方,代码嵌套太多,牵一发而动全身。
Promise
优点:解决了回调函数的问题,可以使用链式结构,代码逻辑清晰。
缺点:无法取消,有时逻辑复杂then太多,then中嵌套then,错误捕获不到正确的位置,只能通过自己的catch或者写reject(erro)的回调来捕获。
async
至此,我们再次引出我们的Async/Await语法糖
优点:以同步代码的方式去写异步,代码逻辑清晰,符合我们平时写的逻辑。缺点:因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。(这个是可以避免的)
@sisterAn 你好,关于你的 3.async/await 我觉得不是很完备。
而且请看一下下面例子是否能够表明清楚
或者有什么不对的地方
针对最后两步的结果,为什么是script end->async1 end?async1到async2之后没有返回到async1?可以这么理解吗:async1中对于async1 end实际上相当于async2中await返回的Promise的then回调吗?所以被归类到异步任务队列中去了?而外部的script end是一个同步的任务,所以先打印出来了
请教一下 执行两次test函数为什么不是 1212345456 呢.
js异步执行流程
Event Loop
首先js执行由三部分组成,用于执行主线程的栈,叫做execution context stack,用于存放在对象数据的heap,一般使用内存,用于存放异步事件队列的event queue
补充一下几乎 所有的异步事件都有专门的线程来管理,event loop只负责将队列中的事件推入调用栈,至于在什么时机推入,都是由专门的线程负责,定时器模块借助CPU时钟来定时推入事件,DOM的更改事件由渲染进程负责
宏任务与微任务
因为其实setTimeout不是JavaScript的api,所以是在宏任务里,JavaScript的代码都是在微任务里执行的,所以执行之后才执行setTimeout的代码
传给setTimeout的回调是异步的,会放在宏任务队列里;
调用Promise的构造函数是同步的,传给Promise.then、Promise.catch、Promise.finally的回调是异步的,会放在微任务队列里;
async函数体内的第一个await表达式是宏任务,下面的都算微任务。
async/await不是同步了嘛,我也缕的不是很清楚,第10题那个回答挺好的
就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
宏观吧,我其实就是比较奇怪,await前面的代码都跳出了线程吗,还是单纯就await 后面的执行跳出了线程, 也可能是我表达有问题, 为啥await以及前面代码的都会先打印 然后才是跳出线程执行外面的,最后才执行await下面的代码。
宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的
写的很好,感谢大大的分享,我还有一个疑惑上面提到的Async/Await await执行的代码 ,会跳出线程,但是await上面的打印会出现在await执行打印的前面,意思是await前面的代码都会跳出线程(异步执行),只有await后面的代码才是同步执行这个意思吗?
会返回一个 被reject 的promise对象。继续绑定catch函数可以捕获到
有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?
上面的解释都很详细了。
我拿 babel es8 编译了下 async/await 结果是这样的
--->
await/async 是通过 Generator/function* 来实现的。 所以 async/await 的相关优势也来自于generator。 Generator 是一个可以暂停 function ,感觉推出 generator 的目的包括不仅限于 Callback Hell 和 Inversion of Control。 感觉整个 community 有在往那个方向走。
结果如下
1. setTimeout
2. Promise
Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。
当JS主线程执行到Promise对象时,
promise1.then() 的回调就是一个 task
promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况
3. async/await
async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
举个例子:
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。
更多可见setTimeout、Promise、Async/Await
宏观任务队列
微观任务队列
的区别