19. 任务(Tasks), 微任务(microtasks), 队列(queues) 和 调度(schedules)
Opened this issue · 0 comments
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 执行顺序
// script start
// script end
// promise1
// promise2
// setTimeout
为什么会这样原文
为了搞清楚缘由,你需要明白 事件循环(event loop)
是如何处理 任务(tasks)
和 微任务(microtasks)
的.当这些名词第一次出现的时候,你可能会感到头疼,没关系,深呼吸...
每一个线程都拥有属于自己的事件循环(event loop)
,也就意味着每一个web worker都会存在自有的事件循环,这样它就能独立运作互不干扰。然而对于同源窗口而言他们共享一个事件循环(event loop),这样他们就可以互相通信了(译者注:根据HTML5.2规范,事件循环分两种,一种是浏览器上下文的,一种是web worker的)。事件循环(event loop)循环执行着进入队列的任务。一个事件循环存在多个任务源,这确保了任务源的执行顺序(译者注:同一个任务源的任务将被添加到相同任务队列,不同任务源的任务可能被添加到不同任务队列),但是在每一次的循环中,浏览器会自主选择哪个源的任务优先执行,这确保了一些性能敏感的任务的优先级,比如用户输入。
任务(tasks,译者注:也叫macro-task)是被调度的,这样浏览器就能很好的将这些任务注入到JavaScript或者Dom中,并且确保这些操作有序执行。在任务之间,浏览器可能会更新视图,从鼠标点击到事件回调,再到html的渲染,以及上面例子提到的setTimeout这些都需要任务调度。
setTimeout等待了给定的延迟时间之后就会为它的回调创建一个新的任务。这就是为什么setTimeout打印在script end之后,因为script end是第一个任务的一部分,而setTimeout对应的是另一个任务,至此,我们快要搞清楚了,我需要你们有足够的耐心看完下一个部分
微任务(Microtasks)通常被安排在当前执行脚本之后,比如对一些批量操作或者一些不会产生新的任务队列的异步处理的反应。每次事件循环中,如果没有其他JavaScript运行并且任务(task)都执行完毕了,那么微任务就会在回调之后被执行。在微任务中排队的任何其他微任务将被添加到队列的末尾并进行处理。微任务包括 MutationObserver、Promise的回调(译者注:微任务包括:process.nextTick(Nodejs), Promises, Object.observe, MutationObserver;任务(tasks)包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering)。
一旦promise执行或者已经执行就会在队列中插入这个微任务的回调。这确保了promise回调是异步的,即使是这个promise已经执行。因此调用.then(yey,nay)是不会立即执行的,这就是为什么promise1和promise2会在script end后打印出来,因为只有在当前脚本执行完成之后微任务才会被执行。也因为微任务通常在下一个任务(tasks)执行之前被执行,所以promise1和promise2会在setTimeout之前打印。