自打 ES 6 推出 Promise 改善了 js 的异步编程之后,eventloop 也越来越多地出现在视野当中。借用大佬们的话:“Event Loop 是 JavaScript 异步编程的核心思想,也是前端进阶必须跨越的一关。同时,它又是面试的必考点。” 话不多说,上代码。
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 中,当我们调用一个方法时,js 会生成执行上下文,这个执行上下文保存着该方法的私有作用域、上层作用域(作用域链)、方法参数、以及这个作用域中定义的变量和 this 的指向。
当一系列的方法被调用的时候,由于 js 是单线程的,这些方法就会按照顺序被排列在一个单独的地方,这个地方就是执行栈。
事件队列是一个存储 异步任务 的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果事件队列不为空的话,事件队列便将第一个任务压入执行栈中运行。
主线程不断重复第三步,也就是 只要主线程空了,就会去读取任务队列 ,该过程不断重复,这就是所谓的 事件循环。
一个银行案例:
以去银行办业务为例,当 5 号窗口柜员处理完当前客户后,开始叫号来接待下一位客户,我们将每个客户比作 宏任务,接待下一个客户 的过程也就是让下一个 宏任务 进入到执行栈。
所以该窗口所有的客户都被放入了一个 任务队列 中。任务队列中的都是 异步操作有了结果的,而不是注册一个异步任务就会被放在这个任务队列中(它会被放到 Task Table 中)。就像在银行中排号,如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号。
在执行宏任务时,是可以穿插一些微任务进去。比如你大爷在办完业务之后,顺便问了下柜员:“最近 P2P 暴雷很严重啊,有没有其他稳妥的投资方式”。柜员就会给出相应的回答。
我们分析一下这个过程,虽然大爷已经办完正常的业务,但又咨询了一下理财信息,这时候柜员肯定不能说:“您再上后边取个号去,重新排队”。所以只要是柜员能够处理的,都会在响应下一个宏任务之前来做,我们可以把这些任务理解成是 微任务。
大爷听罢,扬起 45 度微笑,说:“我就问问。”
柜员 OS:“艹...”
这个例子就说明了:你大爷永远是你大爷。 在当前微任务没有执行完成时,是不会执行下一个宏任务的!
总结一下,异步任务分为 宏任务(macrotask) 与 微任务(microtask) 。宏任务会进入一个队列,而微任务会进入到另一个不同的队列,且微任务要优于宏任务执行。
宏任务:script(整体代码)、setTimeout、setInterval、I/O、事件、postMessage、 MessageChannel、setImmediate (Node.js)
微任务:Promise.then、 MutaionObserver、process.nextTick (Node.js)
1.
setTimeout(() => { console.log(‘A‘); }, 0); var obj = { func: function() { setTimeout(function() { console.log(‘B‘); }, 0); return new Promise(function(resolve) { console.log(‘C‘); resolve(); }); }, }; obj.func().then(function() { console.log(‘D‘); }); console.log(‘E‘);
setTimeout
放到宏任务队列,此时宏任务队列为 [‘A‘]setTimeout
放到宏任务队列,此时宏任务队列为 [‘A‘, ‘B‘]‘C‘
then
放到微任务队列,此时微任务队列为 [‘D‘]console.log(‘E‘);
,打印出 ‘E‘
‘D‘
‘A‘
和 ‘B‘
要注意的是 obj.func().then() 这里,obj.func() 是普通函数/同步代码,后面的 then 才是微任务。
2.
function go() { console.log(5) } let p = new Promise(resolve => { resolve(1); Promise.resolve(go()).then(() => console.log(2)); console.log(4); }).then(t => console.log(t)); console.log(3);
Promise.resolve()
的 then() 方法放到微任务队列,此时微任务队列为 [‘2‘]4
p
的 then() 方法放到微任务队列,此时微任务队列为 [‘2‘, ‘1‘]3
2
和 1
要注意的是 Promise.resolve(go()) 这里也是普通函数/同步代码,当执行到这一行的时候,会立即执行 go(),后面的 then 才是微任务。
至此,与基础 Promise 相关的 eventloop 执行过程分析完毕。
在 es 7 中引入了 async/await 的语法,当他们会与 eventloop 发生什么样的反应呢?
根据定义,我们知道,async/await 仅仅是生成器的语法糖,所以不要怕,只要把它转换成 Promise 的形式即可。下面这段代码是 async/await 函数的经典形式。
async function foo() { // await 前面的代码 await bar(); // await 后面的代码 } async function bar() { // do something... } foo();
await 前面的代码
是同步的,调用此函数时会直接执行;而 await bar();
这句可以被转换成 Promise.resolve(bar())
;await 后面的代码
则会被放到 Promise 的 then() 方法里。改写如下:
function foo() { // await 前面的代码 Promise.resolve(bar()).then(() => { // await 后面的代码 }); } function bar() { // do something... } foo();
所以,开篇的那条镇楼题可以改写成这样:
function async1() { console.log(‘async1 start‘); // 2 Promise.resolve(async2()).then(() => { console.log(‘async1 end‘); // 6 }); } function async2() { console.log(‘async2‘); // 3 } console.log(‘script start‘); // 1 setTimeout(function() { console.log(‘settimeout‘); // 8 }, 0); async1(); new Promise(function(resolve) { console.log(‘promise1‘); // 4 resolve(); }).then(function() { console.log(‘promise2‘); // 7 }); console.log(‘script end‘); // 5
script start
settimeout
添加到宏任务队列,此时宏任务队列为 [‘settimeout‘]
async1
,先打印出 async1 start
,又因为 Promise.resolve(async2())
是同步任务,所以打印出 async2
,接着将 async1 end
添加到微任务队列,,此时微任务队列为 [‘async1 end‘]promise1
,将 promise2
添加到微任务队列,,此时微任务队列为 [‘async1 end‘, promise2]
script end
async1 end
和 promise2
settimeout
关于这道题的争议:大多都是
async1 end
和promise2
的顺序问题。在Chrome 73.0.3683.103 for MAC
和Node.js v8.15.1
测试是async1 end
先于promise2
,在FireFox 66.0.3 for MAC
测试是async1 end
后于promise2
。
关于最后的结果顺序,国外的一篇文章,写的很详细,并且有详细的测试。 结果就是: 在不同的浏览器下,甚至是同一种浏览器的不同版本中,异步任务的执行顺序都会有差异,也就是说他们的优先级并不是完全固定的。注意,只是异步任务的优先级会有所不同,这主要还是各个浏览器的问题。
原文:https://www.cnblogs.com/cc-freiheit/p/10783532.html