宏任务(Macrotask)
- 浏览器中:I/O、setTimeout、setInterval、requestAnimationFrame
- Node中:I/O、setTimeout、setInterval、setImmediate
微任务(Microtask)
- 浏览器中:MutationObserver、Promise.then/.catch/.finally
- Node中:process.nextTick 、Promise.then/.catch/.finally
事件循环
因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。
Event Loop 执行顺序如下所示:
首先执行同步代码,这属于宏任务
当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
执行所有微任务
当执行完所有微任务后,如有必要会渲染页面然后开始下一轮 Event Loop,执行宏任务中的异步代码
new Promise()在实例化的过程中所执行的代码都是同步执行的,而.then、.catch 和 .finally都是异步执行的。
console.log('script start'); // 同步
setTimeout(function () {
console.log('setTimeout1'); // 异步
}, 300);
setTimeout(function () {
console.log('setTimeout2'); // 异步
}, 0);
new Promise(resolve => {
resolve()
console.log('promise1'); // 同步
}).then(() => {
console.log('promise2'); // 异步
})
console.log('script end'); // 同步
// script start
// promise1
// script end
// promise2
// setTimeout2
// setTimeout1
在微任务中注册新的微任务,其执行依然会在宏任务之前执行。
setTimeout(function () {
console.log('setTimeout');
}, 0);
new Promise(resolve => {
resolve()
console.log('promise1');
}).then(() => {
console.log('promise2');
new Promise(resolve1 => {
resolve1()
console.log('promise3');
}).then(() => {
console.log('promise4');
})
})
// promise1
// promise2
// promise3
// promise4
// setTimeout
process.nextTick()先于Promise.then()执行, setTimeout()与setImmediate()执行顺序取决于setTimeout的执行周期与设备性能。
console.log('golbol');
setTimeout(function () {
console.log('timeout1');
process.nextTick(function () {
console.log('timeout1_process');
})
new Promise(function (resolve) {
console.log('timeout1_promise');
resolve();
}).then(function () {
console.log('timeout1_then')
})
})
setImmediate(function () {
console.log('immediate1');
process.nextTick(function () {
console.log('immediate1_process');
})
new Promise(function (resolve) {
console.log('immediate1_promise');
resolve();
}).then(function () {
console.log('immediate1_then')
})
})
process.nextTick(function () {
console.log('process1');
})
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise1_then')
})
setTimeout(function () {
console.log('timeout2');
process.nextTick(function () {
console.log('timeout2_process');
})
new Promise(function (resolve) {
console.log('timeout2_promise');
resolve();
}).then(function () {
console.log('timeout2_then')
})
})
process.nextTick(function () {
console.log('process2');
})
new Promise(function (resolve) {
console.log('promise2');
resolve();
}).then(function () {
console.log('promise2_then')
})
setImmediate(function () {
console.log('immediate2');
process.nextTick(function () {
console.log('immediate2_process');
})
new Promise(function (resolve) {
console.log('immediate2_promise');
resolve();
}).then(function () {
console.log('immediate2_then')
})
})
- 整体代码作为宏任务执行:
宏任务代码 | 微任务队列 |
---|---|
整体代码 | |
- 输出:global | |
- 将setTimeout标记为timeout1添加到宏任务队列中 | |
- 将setImmediate标记为immediate1添加到宏任务队列中 | |
- 将process标记为process1添加到微任务队列中 | |
- 输出:promise1。将promise标记为promise1添加到微任务队里 | |
- 将setTimeout标记为timeout2添加到宏任务队列中 | |
- 将process标记为process2添加到微任务队列中 | |
- 输出:promise2。将promise标记为promise2添加到微任务队里 | |
- 将setImmediate2标记为immediate2添加到宏任务队列中 | |
``` | |
global、promise1、promise2 |
| 宏任务队列 | 微任务队列 |
| --------------------- | ----------------- |
| timeout1、timeout2 | process1、process2 |
| immediate1、immediate2 | promise1、promise2 |
主进程事件执行完,执行微任务队列,process.nextTick比promise.then先执行
process1、process2、promise1_then、promise2_then
- 执行setTimeout宏任务队列
| 宏任务队列 | 微任务队列 |
| --------------------- | ----- |
| timeout1、timeout2 | |
| immediate1、immediate2 | |
分别执行宏任务队列setTimeout任务源
1. 输出:timeout1
1. 将process标记为timeout1_process添加到微任务队列中
1. 输出:timeout1promise。将promise标记为timeout1then添加到微任务队里
1. 输出:timeout2
1. 将process标记为timeout2_process添加到微任务队列中
1. 输出:timeout2promise。将promise标记为timeout2then添加到微任务队里
timeout1、timeout1_promise、timeout2、timeout2_promise
主进程事件执行完,执行微任务队列
| 宏任务队列 | 微任务队列 |
| --------------------- | ------------------------------- |
| | timeout1process、timeout2process |
| immediate1、immediate2 | timeout1then、timeout2then |
执行微任务队列:
timeout1_process、timeout2_process、timeout1_then、timeout2_then
- 执行setImmediate宏任务队列
| 宏任务队列 | 微任务队列 |
| --------------------- | ----- |
| immediate1、immediate2 | |
分别执行宏任务队列setImmediate任务源
1. 输出:immediate1
1. 将process标记为immediate1_process添加到微任务队列中
1. 输出:immediate1promise。将immediate标记为immediate1then添加到微任务队里
1. 输出:immediate2
1. 将process标记为immediate2_process添加到微任务队列中
1. 输出:immediate2promise。将promise标记为immediate2then添加到微任务队里
immediate1、immediate1_promise、immediate2、immediate2_promise
主进程事件执行完,执行微任务队列
| 宏任务队列 | 微任务队列 |
| ----- | ----------------------------------- |
| | immediate1process、immediate2process |
| | immediate1then、 immediate2then |
执行微任务队列:
immediate1_process、immediate2_process、immediate1_then、immediate2_then
整体输出
global、promise1、promise2、
process1、process2、promise1_then、promise2_then、
timeout1、timeout1_promise、timeout2、timeout2_promise、
timeout1_process、timeout2_process、timeout1_then、timeout2_then、
immediate1、immediate1_promise、immediate2、immediate2_promise、
immediate1_process、immediate2_process、immediate1_then、immediate2_then
# 模拟调用堆栈
let macrotask = [] let microtask = [] let call_stack = []
function setMicroTask(cb) { microtask.push(cb) }
function setMacroTask(cb) { macrotask.push(cb) }
global.setTimeout = function setTimeout(cb, time) { macrotask.push(cb) }
function runHandler() { for (var i = 0; i < call_stack.length; i++) { eval(call_stack[i]) } }
function run(cb) { macrotask.push(cb) for (let i = 0; i < macrotask.length; i++) { eval(macrotask[i])() if (microtask.length != 0) { for (let j = 0; j < microtask.length; j++) { eval(microtask[j])() } microtask = [] } } }
call_stack.push(console.log('script start');
)
call_stack.push(setTimeout(function() { console.log('setTimeout'); }, 0);
)
call_stack.push(setMicroTask(()=> { console.log('micro1') setMicroTask(()=> { console.log('micro2') }) })
)
call_stack.push(console.log('script end');
)
run(runHandler)
call_stack.push(console.log('script start');
)
call_stack.push(setTimeout(function() { console.log('setTimeout'); }, 0);
)
call_stack.push(setMicroTask(()=> { console.log('micro1') setMicroTask(()=> { console.log('micro2') }) })
)
call_stack.push(console.log('script end');
)
run(runHandler)
script start script end micro1 micro2 setTimeout
# 未处理的rejection
"未处理的rejection"是指在微任务队列结束时未处理的promise错误。
let promise = Promise.reject(new Error('Promise Failed!'))
window.addEventListener('unhandledrejection', event => { alert(event.reason) })
// Uncaught (in promise) Error: Promise Failed!
创建了一个rejected的promise并没有处理错误,所以会打印一个“未处理的 rejection”。添加.catch()就会解决。
let promise = Promise.reject(new Error('Promise Failed!')) promise.catch(err => alert('caught'))
window.addEventListener('unhandledrejection', event => { alert(event.reason) })
// caught
转载 https://mp.weixin.qq.com/s/PsPLmTRkJ045HOhg2hcONg