Event Loop
看一个最简单的例子:
1 | console.log('script start'); |
输入的结果是什么呢?
- script start
- script end
- promise1
- promise2
- setTimeout
为什么是上面的打印结果呢?
我们需要知道事件循环是如何处理任务和微任务的。
我们知道js是单线程,每个线程都有一个事件循环(Event Loop),所以它可以独立执行,而同一个源的所有窗口共享一个事件循环(Event Loop),所以它们可以同步通信。事件循环不断运行,执行排队的任务。
事件循环具有多个任务源,这些任务源保证该源内部的执行顺序(IndexedDB,Web SQL等不适用此规则),但是浏览器可以在每次循环时选择从哪个源中获取任务。原因是:允许浏览器优先考虑性能敏感的任务,比如用户输入之类的操作
Task
==task==任务的作用是便于浏览器可以从其内部进入JavaScript/DOM域并确保这些操作按顺序发生。在task任务之间,浏览器可以更新渲染。从鼠标单击到事件回调需要调度任务,解析HTML页面也是如此,比如例子中的setTimeout。
setTimeout等待给定的延迟然后为其回调安排为一个新的task任务。这就是为什么setTimeout会被记录(打印)在script end之后,因为script end是第一个task任务的一部分,setTimeout被放在了单独的task任务中。
Microtasks
==Microtasks==微任务通常排在当前正在执行的脚本之后发生,例如对一系列操作作出反应,或者在
没有新的task任务的情况下异步生成。只要没有其他的JavaScript处于正在执行中,那么在每个task任务结束时,就会回调后处理微任务队列。在微任务期间排队的任何其他微任务都会添加到队列的末尾并进行处理。Microtasks包括异步观察者回调模式、以及上面例子中的promise回调。
一旦promise执行,或者它已经结束,它就会为其返回回调排队到一个微任务。这确保了即使promise已经结束,promise的回调也是异步的。因此,.then() 返回的promise回调立即被当做微任务排队。这也就是为什么promise1和promise2是在script end之后记录,因为当前运行的task任务必须在处理microtasks微任务之前完成。promise1和promise2在setTimeout之前记录的原因是:微任务总是在下一个任务之前执行。
不同浏览器的不同表现
有些浏览器的记录结果为:script start,script end,setTimeout,promise1,promise2。原因是:他们在setTimeout之后运行promise回调。很可能他们将promise回调当做新任务的一部分而不是微任务。
这点是可以理解的。因为promise源自ECMAScript而不是HTML。ECMAScript具有类似于微任务的“jobs”概念。但是,普遍的共识是,promises应该是微任务队列的一部分,并且有充分的理由。
参考文章: 点击这里