未分类

eventloop和error异步捕获

Event Loop

先来看一张图:

图1 via 掘金小册:前端面试之道by yck


左边被黄色虚线框起来的部分叫做函数执行栈,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。

图2 via 掘金小册:前端面试之道by yck


当我们在执行JS代码的时候会将所要执行的函数放到执行栈中,遇到异步代码会被挂起并在需要执行的时候加入到其所在的任务队列中(microtask微任务 || macrotask宏任务),一旦执行栈为空,Event Loop 会从任务队列中取出需要执行的方法放入执行栈中执行,所以JS 中的异步还是同步行为。




图1右边部分是宏任务和微任务队列

宏任务和微任务都有哪些?

宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering。

微任务包括 process.nextTick ,promise ,MutationObserver,其中 process.nextTick 为 Node 独有。

图3 via 思否专栏:10分钟理解JS引擎的执行机制by Ziwei


这里很多人会有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话才会先执行微任务。
个人理解如下:

JS捕获异步错误

由于JS是单线程的,所以,我们平时使用的try catch无法捕获到当前循环外的错误。异步任务由 eventloop 加入任务队列,并取出入栈(js 主进程执行栈)执行,而当异步任务取出执行的时候, 执行栈已经退出了,也就是上下文环境已经改变,所以无法捕获异步任务的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 宏任务
const task = () => {
setTimeout(() => {
throw new Error('async error')
}, 1000)
}
// 主任务
function main() {
try {
task();
} catch(e) {
console.log(e, 'err')
console.log('continue...')
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 微任务
const promiseFetch = () =>
new Promise((resolve) => {
resolve();
})

function main() {
try {
// 回调函数里抛出错误
promiseFetch().then(() => {
throw new Error('err')
})
} catch(e) {
console.log(e, 'eeee');
console.log('continue');
}
}

并不是回调函数无法 try catch,而是要看这个回调函数是否和主函数在同一个事件循环中
关于promise.catch
promise 内部的无论是 reject 或者 throw new Error,都可以通过 promise.catch 回调捕获。

这里要跟我们最开始微任务的栗子区分,promise 的微任务指的是 then 的回调,而此处是 Promise 构造函数传入的第一个参数,new Promise 是同步执行的。