理解JavaScript中的macrotask和microtask
异步任务运行机制
JavaScript有一个执行栈和任务队列
主线程依次执行代码 ,会将函数入栈,函数运行完毕后再将该函数出栈
那么遇到 WebAPI(例如:setTimeout, AJAX)这些函数时,这些函数会立即返回一个值,从而让主线程不会在此处阻塞。而真正的异步操作会由浏览器执行,浏览器会在这些任务完成后,将事先定义的回调函数推入主线程的 任务队列 中。
而主线程则会在 清空当前执行栈后,按照先入先出的顺序读取任务队列里面的任务。1
2
3
4
5
6
7
8
9
10
11// 1. 开始执行
console.log('script start'); // 2. 打印字符串 "script start"
setTimeout(
function() { // 5. 浏览器在 0ms 之后将该函数推入任务队列
// 而到第5步时才会被主线程执行
console.log('setTimeout'); // 6. 打印字符串 "setTimeout"
},
0
); // 3. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end'); // 4. 打印字符串 "script end"
// 5. 主线程执行栈清空,开始读取 任务队列 中的任务
浏览器的异步任务的执行机制,核心点为:
异步任务是由浏览器执行的,不管是AJAX请求,还是setTimeout等 API,浏览器内核会在其它线程中执行这些操作,当操作完成后,将操作结果以及事先定义的回调函数放入 JavaScript 主线程的任务队列中
JavaScript 主线程会在执行栈清空后,读取任务队列,读取到任务队列中的函数后,将该函数入栈,一直运行直到执行栈清空,再次去读取任务队列,不断循环
当主线程阻塞时,任务队列仍然是能够被推入任务的。这也就是为什么当页面的 JavaScript 进程阻塞时,我们触发的点击等事件,会在进程恢复后依次执行。
Macrotasks 和 Microtasks
Macrotask 和 microtask 都是属于上述的异步任务中的一种
- macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
- microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver
setTimeout 的 macrotask ,和 Promise 的 microtask 有什么不同呢? 我们通过下面的代码来展现他们的不同点:1
2
3
4
5
6
7
8
9
10console.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');
在这里,setTimeout的延时为0,而Promise.resolve()也是返回一个被resolve了promise对象,即这里的then方法中的函数也是相当于异步的立即执行任务,那么他们到底是谁在前谁在后?
我们看看最终的运行结果(node 7.7.3):1
2
3
4
5"script start"
"script end"
"promise1"
"promise2"
"setTimeout"
这里的运行结果是Promise的立即返回的异步任务会优先于setTimeout延时为0的任务执行。
原因是任务队列分为 macrotasks 和 microtasks,而Promise中的then方法的函数会被推入 microtasks 队列,而setTimeout的任务会被推入 macrotasks 队列。在每一次事件循环中,macrotask 只会提取一个执行,而 microtask 会一直提取,直到 microtasks 队列清空。
而事件循环每次只会入栈一个 macrotask ,主线程执行完该任务后又会先检查 microtasks 队列并完成里面的所有任务后再执行 macrotask
1 | setImmediate(function(){ |