宏任务微任务

275 阅读4分钟

宏任务(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 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。

image.png

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