JS异步错误捕获的一些事小结

我们都知道 try catch 无法捕获 setTimeout 异步任务中的错误,那其中的原因是什么。以及异步代码在 js 中是特别常见的,我们该怎么做才比较?

无法捕获的情况

function main() { try { setTimeout(() => { throw new Error('async error') }, 1000) } catch(e) { console.log(e, 'err') console.log('continue...') } } main();

这段代码中,setTimeout 的回调函数抛出一个错误,并不会在 catch 中捕获,会导致程序直接报错崩掉。

所以说在 js 中 try catch 并不是说写上一个就可以高枕无忧了。难道每个函数都要写吗,
那什么情况下 try catch 无法捕获 error 呢?

异步任务

宏任务的回调函数中的错误无法捕获

上面的栗子稍微改一下,主任务中写一段 try catch,然后调用异步任务 task,task 会在一秒之后抛出一个错误。

// 异步任务 const task = () => { setTimeout(() => { throw new Error('async error') }, 1000) } // 主任务 function main() { try { task(); } catch(e) { console.log(e, 'err') console.log('continue...') } }

这种情况下 main 是无法 catch error 的,这跟浏览器的执行机制有关。异步任务由 eventloop 加入任务队列,并取出入栈(js 主进程)执行,而当 task 取出执行的时候, main 的栈已经退出了,也就是上下文环境已经改变,所以 main 无法捕获 task 的错误。

事件回调,请求回调同属 tasks,所以道理是一样的。eventloop 复习可以看这篇文章

微任务(promise)的回调

// 返回一个 promise 对象 const promiseFetch = () => new Promise((reslove) => { reslove(); }) function main() { try { // 回调函数里抛出错误 promiseFetch().then(() => { throw new Error('err') }) } catch(e) { console.log(e, 'eeee'); console.log('continue'); } }

promise 的任务,也就是 then 里面的回调函数,抛出错误同样也无法 catch。因为微任务队列是在两个 task 之间清空的,所以 then 入栈的时候,main 函数也已经出栈了。

并不是回调函数无法 try catch

很多人可能有一个误解,因为大部分遇到无法 catch 的情况,都发生在回调函数,就认为回调函数不能 catch。

不全对,看一个最普通的栗子。

// 定义一个 fn,参数是函数。 const fn = (cb: () => void) => { cb(); }; function main() { try { // 传入 callback,fn 执行会调用,并抛出错误。 fn(() => { throw new Error('123'); }) } catch(e) { console.log('error'); } } main();

结果当然是可以 catch 的。因为 callback 执行的时候,跟 main 还在同一次事件循环中,即一个 eventloop tick。所以上下文没有变化,错误是可以 catch 的。

根本原因还是同步代码,并没有遇到异步任务。

promise 的异常捕获

构造函数

先看两段代码:

function main1() { try { new Promise(() => { throw new Error('promise1 error') }) } catch(e) { console.log(e.message); } } function main2() { try { Promise.reject('promise2 error'); } catch(e) { console.log(e.message); } }

以上两个 try catch 都不能捕获到 error,因为 promise 内部的错误不会冒泡出来,而是被 promise 吃掉了,只有通过 promise.catch 才可以捕获,所以用 Promise 一定要写 catch 啊。

然后我们再来看一下使用 promise.catch 的两段代码:

// reject const p1 = new Promise((reslove, reject) => { if(1) { reject(); } }); p1.catch((e) => console.log('p1 error'));

// throw new Error const p2 = new Promise((reslove, reject) => { if(1) { throw new Error('p2 error') } }); p2.catch((e) => console.log('p2 error'));

promise 内部的无论是 reject 或者 throw new Error,都可以通过 catch 回调捕获。

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

then

那 then 之后的错误如何捕获呢。

function main3() { Promise.resolve(true).then(() => { try { throw new Error('then'); } catch(e) { return e; } }).then(e => console.log(e.message)); }

只能是在回调函数内部 catch 错误,并把错误信息返回,error 会传递到下一个 then 的回调。

用 Promise 捕获异步错误

const p3 = () => new Promise((reslove, reject) => { setTimeout(() => { reject('async error'); }) }); function main3() { p3().catch(e => console.log(e)); } main3();

把异步操作用 Promise 包装,通过内部判断,把错误 reject,在外面通过 promise.catch 捕获。

async/await 的异常捕获

首先我们模拟一个请求失败的函数 fetchFailure,fetch 函数通常都是返回一个 promise。

main 函数改成 async,catch 去捕获 fetchFailure reject 抛出的错误。能不能获取到呢。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/6eea9cedb6c69fbd6e115d698db24b57.html