错误处理
try-catch
完整语法:
try {
// 代码...
} catch (err) {
// 错误捕获
// 若无错误,自动忽略
} finally {
// 无论是否发生错误都会执行
}
- 首先,一行一行执行
try块的内容 - 执行遇到错误时,引擎会控制流转向
catch块,进行错误处理,否则忽略catch模块 - 无论是否有错误,最后都会执行
finally块内容finally子句适用于try...catch的 任何 出口,包括显式的return
运行与解析
错误处理只能对于运行时错误进行捕获,而对于解析时间发生的错误则无能为力
- JavaScript脚本首先会被引擎进行解析,处于解析时间,主要处理语法错误
- 解析完成后会进入引擎进行解释运行,进入运行时间,
try-catch开始工作
异步错误
try-catch无法在外部处理非同步代码的错误
try {
setTimeout(function() {
noSuchVariable; // 脚本将在这里停止运行
}, 1000);
} catch (err) {
console.info("不工作");
}
setTimeout部分属于计划执行函数,不会立即执行- 当开始执行
setTimeout时,意味着引擎已经离开了同步代码块try-catch - 此时的错误无法被
try-catch捕获处理
正确应该是放入setTimeout内部进行捕获
setTimeout(function() {
try {
noSuchVariable; // try...catch 处理 error 了!
} catch {
console.info("error 被在这里捕获了!");
}
}, 1000);
Error对象
当错误发生时,会将错误的相关信息全部放入catch (err)中的err对象(命名可以随意)中。也允许不捕获错误对象的数据,直接 catch {} 即可
Error对象结构:
name:错误的名称(ReferenceError、SyntaxError等)message:错误对象创建时传入的错误描述stack:包含有关导致error的嵌套调用序列的信息,用于调试,非标准实现
try {
lalala; // error, variable is not defined!
} catch (err) {
console.info(err.name); // ReferenceError
console.info(err.message); // lalala is not defined
console.info(err.stack); // ReferenceError: lalala is not defined at (...call stack)
// 也可以将一个 error 作为整体显示出来
// error 信息被转换为像 "name: message" 这样的字符串
console.info(err); // ReferenceError: lalala is not defined
}
抛出错误
语法:
throw error_object;
// 示例
let error = new SyntaxError(message);
throw error;
// 或者
throw new SyntaxError(message);
建议做法:捕获错误时,只处理特定的错误,对于其余未知的错误,使用throw重新抛出,让上一级进行处理。
错误类型判断方法:
err instanceof ReferenceError- 读取
err.name - 读取
err.constructor.name
全局catch
一般用于浏览器环境。
Node中:通过给程序订阅一个事件进行全局处理
process.on("uncaughtException")
浏览器中:给全局添加一个错误处理方法即可,一般是给开发人员或者用户进行提醒,无法进行修复。
可以在onerror函数中添加网络请求,发生错误时将错误信息发送到服务提供方进行分析。
window.onerror = function(message, url, line, col, error) {
// ...
};
message:error信息。url:发生error的脚本的URL。line,col:发生error处的代码的行号和列号。error:error对象。
只能在网页中复现:
<script>
window.onerror = function(message, url, line, col, error) {
console.info(`${message}\n At ${line}:${col} of ${url}`);
};
function readData() {
badFunc(); // 啊,出问题了!
}
readData();
</script>
自定义Error
扩展Error
可以根据当前的错误场景进行定义合适的错误类型
class ValidationError extends Error {
constructor(message) {
// 必须先调用基类的构造函数
super(message);
this.name = "ValidationError";
}
}
// 用法
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("No field: age");
}
if (!user.name) {
throw new ValidationError("No field: name");
}
return user;
}
// try..catch 的工作示例
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
console.info("Invalid data: " + err.message); // Invalid data: No field: name
} else if (err instanceof SyntaxError) { // (*)
console.info("JSON Syntax Error: " + err.message);
} else {
throw err; // 未知的 error,再次抛出 (**)
}
}
深入继承
可以对自定义的错误类进行再次细化
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
// 用法
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
// try..catch 的工作示例
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
console.info("Invalid data: " + err.message); // Invalid data: No property: name
console.info(err.name); // PropertyRequiredError
console.info(err.property); // name
} else if (err instanceof SyntaxError) {
console.info("JSON Syntax Error: " + err.message);
} else {
throw err; // 未知 error,将其再次抛出
}
}
包装异常
对于同一场景下发生的不同错误,可以统一往上层抛出一个异常。
当我们只需要知道这里发生了哪种错误,而不需要具体的错误信息时使用。
class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = 'ReadError';
}
}
class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }
function validateUser(user) {
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
}
function readUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError("Syntax Error", err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
// 实际发生的 ValidationError
// 但往上层抛出 ReadError
if (err instanceof ValidationError) {
throw new ReadError("Validation Error", err);
} else {
throw err;
}
}
}
try {
readUser('{bad json}');
} catch (e) {
if (e instanceof ReadError) {
console.info(e);
// Original error: SyntaxError: Unexpected token b in JSON at position 1
console.info("Original error: " + e.cause);
} else {
throw e;
}
}