一文作死js中同/异步任务、宏/微任务的执行顺序
? 因为这两天面试有遇到相关的问题,以及在维护外包项目时遇到的种种相关的奇葩异步乱用的问题,决定好好捋捋这几个名词在实际中的应用。
一、队列类型
? js是单线程编程语言,所以js的执行顺序是按语句的顺序去排列的。
尽管js是单线程语言,但js的执行任务可以分为两类:
- 同步任务:就是在主线程上的任务,顺序到达后马上执行;
- 异步任务:在主线程上异步执行的任务,顺序到达后并不会马上执行,但会被排在任务队列里,执行完同步任务后按队列执行异步任务。
二、执行
不论理没了解,不废话,直接刚:
1、下面先看同步任务:A
console.log('start');function task() { console.log('task');}task();console.log('end');在控制台可以看到输出start task end,这就是同步任务,只需顺序到达马上执行。
2、接着看异步任务
异步任务有ES5的settimeout、setinterval以及ES6的promise。
settimeout
接着上面的代码,先看前者:B
console.log('start');setTimeout(() => { console.log('s1');});function task() { console.log('task');}task();console.log('end');可以看到,虽然settimeout在task函数的前面,但s1在最后输出,表明settimeout是异步任务,排在主线程之外的队列中执行。
? 为了进一步验证,我们添加难度,看下面代码:C
console.log('start');setTimeout(() => { console.log('s1');});function task() { console.log('task'); setTimeout(() => { console.log('s2'); });}task();setTimeout(() => { console.log('s3');});console.log('end');? 刷新浏览器,可以看到控制台在最后按顺序输出s1 s2 s3,这表明所有的settimeout事件在同一队列里,所以队列里的settimeout按顺序执行。
? 在日常开发过程中我们经常会遇到异步嵌套异步,假如同个队列内部都有异步,这时候的执行又是怎么的呢?接下来继续添加难度:D
console.log('start');setTimeout(() => { console.log('s1'); setTimeout(() => { console.log('s4'); });});function task() { console.log('task'); setTimeout(() => { console.log('s2'); });}task();setTimeout(() => { console.log('s3'); setTimeout(() => { console.log('s5'); });});console.log('end');?上面我们在两个settimeout里分别新添加了一个settimeout,这时候的执行顺序会不会有什么不同呢?
?继续刷新浏览器,在控制台看输出...end s1...s5,怎样样,有没有觉得很奇怪?
?假如不了解js的任务队列执行顺序问题,会对上面的代码执行结果表示一脸萌,起码当初的我就是这种表情。
?所以接着C的思路在D的表现:主线程任务先执行,异步任务推入任务队列,主线程任务执行完成之后按顺序继续执行任务队列的任务;在任务队列里有二维异步任务,推入第二条队列,执行完第一队列后,继续执行第二队列;
?看到这里,应该对js的任务队列有肯定的了解了吧,假如还不了解,就按照上面的例子换着法子使劲折腾就对了,实践出真知,在学习编程的时候是最最真的道理了。
?看完settimeout的例子,接下来我们继续看 promise,至于setinterval就暂时不探讨了。
promise
为了循序渐进,我们接着B的例子一点点添加难度,继续:E
console.log('start');setTimeout(() => { console.log('s1');});function task() { console.log('task');}new Promise(resolve => { console.log('p1'); resolve(true);});task();console.log('end');? 刷新浏览器可以看到输出start p1 task end s1,为啥?
? 起因所在,就要引申出宏任务和微任务的概念了。
? 假如对js的事件循环机制了解不深,到这里或者许就要懵了,既有同步任务异步任务还有任务队列,现在又多了了宏观任务和微观任务,这咋判断?接下来我就讲讲这几个概念的…我也讲不清楚,所以我就找了一段网上我觉得形容的比较好的:
js是单线程语言,对于异步操作只能先把它放在一边,按照某种规则按先后顺序放进一个容器(其实就是存入宏观任务和微观任务队列中),先解决同步任务,再解决异步任务。异步任务分为 [ 宏观任务队列、微观任务队列 ]
按照规定,能发起宏观任务的方法有:
script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境);
微观任务的方法有:
Promise.then、MutaionObserver、process.nextTick(Node.js 环境),async/await实际上是promise+generator的语法糖,也就是promise,也就是微观任务;
? 有promise就少不了then,所以在E基础上我们加上then,再看输出:F
console.log('start');setTimeout(() => { console.log('s1');});function task() { console.log('task');}new Promise(resolve => { console.log('p1'); resolve(true);}).then(() => { console.log('then');});task();console.log('end');? 之前的输出是start p1 task end s1,加了then之后输出start p1 task end then s1,then在s1之前输出。
? 按照上面对几个概念的形容,promise的执行属于微任务,settimeout属于宏任务,而F的输出表明微任务先于宏任务执行。可是,这是绝对的吗?下面我们继续作:G
console.log('start');setTimeout(() => { console.log('s1'); new Promise(resolve => { console.log('p2'); resolve(true); }).then(() => { console.log('then2'); });});function task() { console.log('task');}new Promise(resolve => { console.log('p1'); resolve(true);}).then(() => { console.log('then');});task();console.log('end');? 可以看到新增的微任务的结果p2 then2在最后输出,这是不是跟上面“微任务先于宏任务执行”有冲突?仔细一想,其实不然,在上面G的代码执行顺序来看,可以分为几个执行步骤:
先执行同步任务:
start、p1、task、end,为何p1会先执行?由于这时候的p1其实还在同步任务里,then之后的操作才在异步任务队列中;接着执行异步任务,而异步任务分为宏任务和微任务,微任务先于宏任务执行,所以第二步执行:
then、s1;最后执行宏任务内部的异步,也就是微任务:
p2、then2。? 所以从上面可以总结:先执行主线程的同步任务,这是第一梯队;若有异步,先执行异步里的微任务也就是then内部的操作,这是第二梯队;而后执行宏任务也就是settimeout内部的操作,这是第三梯队;假如第三梯队中又有微任务,继续执行,这是第四梯队。
为了验证,我们继续作:H
console.log('123');setTimeout(() => { console.log('s1'); new Promise(resolve => { console.log('res3'); resolve(true); }).then(() => { console.log('then3'); });});setTimeout(() => { console.log('s2');}, 0);function task() { console.log('task'); setTimeout(() => { console.log('s3'); }); new Promise(resolve => { console.log('res2'); resolve(true); }).then(() => { console.log('then2'); });}new Promise(resolve => { console.log('res'); setTimeout(() => { console.log('ps'); }); resolve(true); console.log('after');}).then(() => { console.log('then');});console.log('456');task();console.log('end');? 看完上面的代码,先别管结果如何,有没有想吐槽:变态!谁会写这样的代码!你别不信,其实这是我在接外包项目debug的时候经常遇到的事。由于他们的代码往往是以完成任务为目标而得出来的,至于完成的过程是怎么的,他们完全不会关注,这就导致代码内部会出现如宏观任务嵌套微任务,微任务又与宏任务以及微任务相互并行的现象,至于中间会出现什么问题,They don’t care…扯远了,想知道上面的结果是啥,自己看。
async…await
? 最后看看promise的语法糖async await在队列中又有什么不同, 下面继续刚:I
async function async1() { console.log('async1-start'); await async2(); console.log('async1-end');}async function async2() { console.log('async2'); new Promise(function(resolve) { console.log('resolve1'); resolve(); }) .then(() => { console.log('then'); }) .then(() => { console.log('then2'); });}console.log('script-start');setTimeout(function() { console.log('setTimeout');}, 0);async1();new Promise(function(resolve) { console.log('promise1'); resolve();}) .then(function() { console.log('promise2');}) .then(function() { console.log('promise3');});console.log('script-end');? 上面代码最后输出是script-start async1-start async2 resolve1 promise1 script-end then async1-end promise2 then2 promise3 setTimeout。
? 假如不看结果,自己料想一遍,能得到正确的输出吗?假如了解了代码从A~H的意思,基本上是能得到正确答案的,下面我们梳理一下执行顺序:
同步任务:
- script-start先执行–输出
script-start; - 接着执行asyn1()函数,函数内部的async1-start在函数的同步序列中,紧接着被输出,因为async的缘故,后面的语句会被放到微任务队列-1–输出
async1-start; - 而后到async2()函数,首先会输出async2,接着因为await的起因使得当前函数变为同步,所以resolve1尽管在promise内部,但还在主线程上,所以也马上被输出,then放在微任务队列-2中–输出
async2 resolve1; - 执行完asyn1()函数的同步任务,继续向下执行,遇到
new Promise,内部的promise1在主线程上,马上被输出,then放在微任务队列-3中–输出promise1; - 最后执行输出
script-end
- script-start先执行–输出
异步任务:
? 先执行微任务:
从上面执行顺序可知现在有微任务队列1/2/3,按理是按照数字顺序执行的,但既然这节探讨的是async awiat,它的作用就是把异步函数当成同步解决,也就是说等当前的异步执行完之后,才会继续向下执行,所以在这里是先执行微任务2,才会执行微任务1,最后执行微任务3;
至于第二层链式then,由于没有async await语法让它提前执行,所以放在第二梯队的微任务里。
所以第一次微任务–输出
then async1-end promise2;上面执行完第一梯队的微任务,接着执行第二梯队微任务,也就是第二层then语句,所以按顺序输出–
then2 promise3;执行完微任务,最后执行宏任务:
宏任务只有一个setTimeout,所以最终输出–
setTimeout。执行完毕。
最后,假如把
await async2()中的await去掉,又会发生什么?请自行验证……
总结
? 所以综上所述,简单总结一句话就是:同步任务结束后,先解决微观任,然务后解决宏观任务,宏观任务内部解决重复上述动作。
? 以上内容个人实践总结,如有不对欢迎拍砖??,希望能帮到大家??。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 一文作死js中同/异步任务、宏/微任务的执行顺序