关于JavaScript异常捕获的几点建议
前言
为了解决JavaScript回调地狱的问题,ES6+引入了Promise,通过将返回值代理到Promise中,并结合pending, rejected, fulfilled三种状态的转化返回Promise,将异步的返回值存储在未来某一时刻可以用到的promise中,方便解析。
Promise通过then()链的方式调用,发生错误时通过rejected promise传递错误,可以像try...catch一样捕获和处理异常,但是使用不当可能导致意料之外的行为,本文列举了一些常见的使用错误,在使用中应当注意。
1. .then链式调用异步,捕获不到异常
👉🏻现象:在.then()链中调用异步函数,某些调用方式不同会导致捕获不到异步抛出的异常
const before = () => {
const p = new Promise((resolve, reject) => {
// resolve('before')
console.log('ok');
reject(new Error('error!!!'));
});
return p;
};
const handleClick = () => {
const p = new Promise((resolve, reject) => {
resolve(true);
});
p
// ❌ catch不到
.then(() => {
before();
})
// .then(before)
// .then(async () => {
// await before();
// })
.then(() => {
console.log('then');
})
.catch(e => {
console.error(e);
});
};
💡原因
缺少return语句:before()没有return,返回undefined,无错误抛出,后续的then链会继续执行,但是因为错误没有被捕获/正确处理,会导致页面或应用程序崩溃。
❓疑惑
rejected promise不就是异常吗,那为什么没有触发呢?
⭐️ The return is implicit under some circumstances
实际上rejected promise并不是异常,如果不被return出去,就不会触发error机制(类似的,如果创建一个新的错误对象,但是没有throw,那么同样也不会触发异常)
实际上,rejected promise并不是异常,如果没有return出去,那么就不会触发error
2. try...catch无法捕获异步函数中通过return返回的rejected error.
👉🏻需要使用await接收异步函数的返回,再将结果return出去
const a = async () => {
return new Promise((resolve, reject) => {
reject('error')
})
};
const b = async () => {
try {
// ❌捕获不到
// return a();
const res = await a();
return res;
}
catch (e) {
console.log('catch exception!!!')
}
}
b();
3. 在Promise的构造函数中使用async会导致异常捕获不到
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
💡第一段代码能够捕获到异常,因为异常由Promise的构造函数直接reject出去;第二段代码不能,因为async关键字会隐式生成一个新的promise,由async函数自身处理。
4.🪜JavaScript Reject V.S. Throw
都是用于抛出异常,reject和throw有什么区别,适用于什么场景
先简单对比一下:
- 都可用于触发错误机制
- reject是promise的一部分,结合promise使用;throw不受限制
- throw之后的语句都不会执行了,reject之后的语句会正常执行,但如果reject前边有return,那么其后的语句同样也不会继续执行
但实际上,这俩并没有优劣之分,但是在某种场景下throw是无效的。在promise的回调中throw可以正常使用,一旦出现异步,throw就无效了。
比如:
new Promise(function() {
setTimeout(function() {
throw 'or nah';
// return Promise.reject('or nah'); also won't work
}, 1000);
}).catch(function(e) {
console.log(e); // doesn't happen
});
正确捕获的方式:
new Promise((resolve, reject) => {
setTimeout(function() {
reject('or nah');
}, 1000);
}).catch(function(e) {
console.log(e); // works
});
5. 异常的正确处理方式
异常需要被正确捕获。
-
嵌套的try...catch
如果代码中有嵌套的
try...catch
,那么异常会被内部的catch
捕获:内层异常被处理,那么外层就捕获不到了;内层直接throw,那么内层try...catch无意义 -
.then
链的异常捕获,需要保证异常在UI
层被捕获。不要过早的吞掉异常,需要保证
catch
在最终调用处调用,避免异常暴露导致程序或页面崩溃。
小结
rejected promise和throw都可用于触发异常,没有优劣之分;
promise回调中存在异步,那么rejected promise必要要return出去才可被后续处理,否则返回undefined,即使存在异常也不会被处理,会导致页面或应用程序崩溃;
promise回调中的异步函数,执行结果需要被return出去异常才会被捕获:要么直接作为回调参数,要么使用async...await执行,要么使用return
捕获promise回调中异步函数的异常,throw是无效的,创建一个新的rejected promise也是无效的,需要使用当前promise的rejecte函数
在promise的构造函数中使用async关键字会导致异常捕获不到,因为async关键字会隐式生成一个新的promise,由async函数自身处理
参考
转载自:https://juejin.cn/post/7157952391009009671