您的位置:首页 > Web前端 > JavaScript

Javascript错误处理——try...catch

2017-07-29 21:22 686 查看

Javascript错误处理——try…catch

无论我们编程多么精通,脚本错误怎是难免。可能是我们的错误造成,或异常输入,错误的服务器端响应以及无数个其他原因。

通常,当发送错误时脚本会立刻停止,打印至控制台。

try...catch
语法结构可以捕获错误并可以做更多的事情,而不是直接停止。

“try…catch” 语法

try...catch
结构有两个语句块,即
try
,然后
catch


try {

// code...

} catch (err) {

// error handling

}


工作流程如下:

首先
try{...}
代码块执行。

如果没有错误,那么
catch(err)
被忽略:执行到
try
结尾时,跳过
catch
块。

如果发生错误,那么
try
块中执行停止,控制流进入
catch(err)
.
err
(可以是任何名称)变量包含错误发生相关的信息信息对象。



所以,
try{...}
块内的错误不会让脚本停止:我们有机会在
catch
块内处理。让我们看更多的示例。

无错误示例:显示
alert (1)
(2)
:

try {

alert(‘Start of try runs’); // (1) <–

// …no errors here

alert(‘End of try runs’); // (2) <–

} catch(err) {

alert(‘Catch is ignored, because there are no errors’); // (3)

}

alert(“…Then the execution continues”);

带错误示例:显示
(1)
(3)
:

try {

alert(‘Start of try runs’); // (1) <–

lalala; // error, variable is not defined!

alert(‘End of try (never reached)’); // (2)

} catch(err) {

alert(
Error has occured!
); // (3) <–

}

alert(“…Then the execution continues”);

try..catch仅作用在运行时错误

try..catch
,代码必须是运行时,换句话说,必须是有效的Javascript代码。

如果代码语法错误不会工作,举例,下面不会捕获大括号错误:

try {
{{{{{{{{{{{{
} catch(e) {
alert("The engine can't understand this code, it's invalid");
}


Javascript引擎首先读代码,然后运行。发生在读阶段错误称为“解析时”错误,不可恢复,因为引擎不理解代码。

所以,
try...catch
仅能处理有效代码中的错误,被称为“运行时”错误,有时也称为“异常”。

try..catch同步执行

如果在预定的(scheduled)代码发生异常,如
setTimeout
,那么
try...catch
不捕获异常:

try {
setTimeout(function() {
noSuchVariable; // script will die here
}, 1000);
} catch (e) {
alert( "won't work" );
}


因为
try...catch
包装了
setTimeout
调用预定函数。但函数会延后执行,此时引擎已经立刻了
try...catch
结构。

为了捕获预定执行函数内的异常,
try...catch
必须在函数内部:

setTimeout(function() {
try {
noSuchVariable; // try..catch handles the error!
} catch (e) {
alert( "error is caught here!" );
}
}, 1000);


错误对象

当错误发生时,Javascript生成包含细节信息的对象,并作为参数传递给
catch
块:

try {
// ...
} catch(err) { // <-- the "error object", could use another word instead of err
// ...
}


对所有内置错误,
catch
内的错误对象主要有两个属性:

name

错误名称,一个未定义变量是
“ReferenceError”


message

错误信息的文本描述。

在大多数环境中有其他非标准属性,被广泛使用和支持的一个是:stack

当前调用栈:关于导致错误的嵌套调用序列,用于调试目的。

举例:

try {
lalala; // error, variable is not defined!
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at ...

// Can also show an error as a whole
// The error is converted to string as "name: message"
alert(err); // ReferenceError: lalala is not defined
}


使用“try…catch”

让我们探索一个真实的用例:

我们知道,Javascript支持方法
JSON.parse(str)
,用于读json值。通常用于解析从网络中接收json数据,如服务器端或其他来源。接收并调用
JSON.parse
,如下:

let json = '{"name":"John", "age": 30}'; // data from the server

let user = JSON.parse(json); // convert the text representation to JS object

// now user is an object with properties from the string
alert( user.name ); // John
alert( user.age );  // 30


如果
json
非标准的,JSON.parse产生错误,脚本为停止。这样我们会满意吗?当然不会!

如果数据带有某种错误,用户完全不知道发送什么(除非打开开发控制台)。没人喜欢发生错误时脚本停止且没有任何错误信息。

让我们使用
try...catch
处理错误:

let json = "{ bad json }";

try {

let user = JSON.parse(json); // <-- when an error occurs...
alert( user.name ); // doesn't work

} catch (e) {
// ...the execution jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
alert( e.name );
alert( e.message );
}


这里我们使用
catch
块仅显示信息,也可以做更多:新的网络请求,建议另一种选择,发送错误信息至日志等,总之都比代码直接停止好。

抛出我们自己的错误

如果json语法正在,但没有需要的name属性,会怎么样?

如下:

let json = '{ "age": 30 }'; // incomplete data

try {

let user = JSON.parse(json); // <-- no errors
alert( user.name ); // no name!

} catch (e) {
alert( "doesn't execute" );
}


这里
JSON.parse
执行正常,但缺少name属性,实际对我们来说是个错误。为了统一错误处理,我们需要使用
throw
操作。

Throw操作

该操作产生一个错误。语法:

throw <error object>


技术上,可以使用任何内容作为错误对象。可以是原始类型,如数字或字符串,但最好使用对象,并带有name和message属性(与内置错误对象兼容)。

Javascript有很多内置标准错误构造器:Error、SyntaxError、ReferenceError、TypeError等其他。我们也能使用他创建错误对象。

语法:

let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...


对内置错误对象(仅为错误对象),name属性正好是构造函数的名称,message是构造函数参数。

let error = new Error("Things happen o_O");

alert(error.name); // Error
alert(error.message); // Things happen o_O


让我们看
JSON.parse
生成的这种错误:

try {
JSON.parse("{ bad json o_O }");
} catch(e) {
alert(e.name); // SyntaxError
alert(e.message); // Unexpected token o in JSON at position 0
}


如我们所见,错误为:
SyntaxError


在我们的示例中,缺省name属性,也可以视为语法错误,假设users必须有个name属性,所以我们抛出错误:

let json = '{ "age": 30 }'; // incomplete data

try {

let user = JSON.parse(json); // <-- no errors

if (!user.name) {
throw new SyntaxError("Incomplete data: no name"); // (*)
}

alert( user.name );

} catch(e) {
alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name
}


在星号行,
throw
操作产生
SyntaxError
错误,并带有给定的
message
,与Javascript自身生成的错误一致。try块中的执行立刻停止,控制流跳至
catch
块。

现在
catch
变成了独立处理所有错误的块:
JSON.parse
和其他错误。

再次抛出错误

上面的示例,我们使用
try...catch
处理不正确的数据,但也可能是其他异常发生在
try...catch
块中,如变量未定义或其他,不仅是“不正确的数据”。

如下:

let json = '{ "age": 30 }'; // incomplete data

try {
user = JSON.parse(json); // <-- forgot to put "let" before user

// ...
} catch(err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (not JSON Error actually)
}


当然,一切都是可能的!程序人员造成的错误,即使被大量使用的开源工具——可能会突然发现一个疯狂的bug,导致了可怕的黑客攻击(就像使用ssh工具发生的那样)。

在我们的示例中,
try...catch
是为了处理不正确数据错误,但实际上,catch捕获try块中所有的错误。如有一个异常错误,仍然显示“JSON Error”消息,这样的错误也使代码更难调试。

幸运的是,我们能发现捕获的错误是那种类型,示例,从其name属性:

try {
user = { /*...*/ };
} catch(e) {
alert(e.name); // "ReferenceError" for accessing an undefined variable
}


规则很简单。

应该仅处理已知错误,重新抛出其他错误

详细的重新抛出错误解释如下:

捕获所有错误

catch(err){...}
块中,我们分析错误对象
err


如果不知道如何处理,那么通过
throw err
抛出错误

下面的代码,我们使用重新抛出错误,这样
catch
仅处理
SyntaxError
:

let json = '{ "age": 30 }'; // incomplete data
try {

let user = JSON.parse(json);

if (!user.name) {
throw new SyntaxError("Incomplete data: no name");
}

blabla(); // unexpected error

alert( user.name );

} catch(e) {

if (e.name == "SyntaxError") {
alert( "JSON Error: " + e.message );
} else {
throw e; // rethrow (*)
}

}


catch
块内部星号行抛出错误,其可以被外部的
try...catch
结构块捕获(如果存在),或直接停止脚本。

所以
catch
块实际上仅处理已知错误,并忽略所有其他错误。

下面示例演示这样的错误被多级
try...catch
块处理。

function readData() {
let json = '{ "age": 30 }';

try {
// ...
blabla(); // error!
} catch (e) {
// ...
if (e.name != 'SyntaxError') {
throw e; // rethrow (don't know how to deal with it)
}
}
}

try {
readData();
} catch (e) {
alert( "External catch got: " + e ); // caught it!
}


这里
readData
仅知道如何处理SyntaxError错误,而外部的
try...catch
知道如何处理任何错误。

try…catch…finally

等等,还没有完。

结构
try...catch
可以有多个代码子句:
finally
,如果存在,所有情况都会执行。

try
之后,如果没有错误情况

catch
之后,如果有错误

扩展语法类似如下:

try {
... try to execute the code ...
} catch(e) {
... handle errors ...
} finally {
... execute always ...
}


请尝试运行下面代码:

try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
} catch (e) {
alert( 'catch' );
} finally {
alert( 'finally' );
}


代码有两条执行路径:

如果回答“Yes”产生一个错误,那么执行路径为
try->catch->finally
.

如果回调“No”,那么路径为
try->finally
.

子句
finally
通常应用场景为:在
try...catch
块之前开始做某事,无论结果如何都需要终止。

举例,我们想衡量斐波拉切函数
fib(n)
执行时间,很自然,我们需要在执行前和结束后衡量。但如果在函数调用期间有错误?特别是,下面代码中的fib(n)的实现将返回一个针对负数或非整数的错误。

不管发生什么,子句
finally
很适合去完成时间测量。

finally
负责在两种场景下测试执行时间——成功执行
fib
函数和错误情况:

let num = +prompt("Enter a positive integer number?", 35)

let diff, result;

function fib(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("Must not be negative, and also an integer.");
}
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
result = fib(num);
} catch (e) {
result = 0;
} finally {
diff = Date.now() - start;
}

alert(result || "error occured");

alert( `execution took ${diff}ms` );


你可以根据提示输入35,检查代码运行——执行正常,try之后执行finally。再次输入-1,立刻产生错误,执行花费0ms,正确完成时间测量。

换句话说,有两种方法可以退出函数:要么return,要么抛出错误。finally子句柄都会处理。

在try…catch…finally块中的变量是局部变量

注意在上面代码
result
diff
变量,是在
try...catch
块之前声明的。

否则,如果使用let在{…}块里面,则只能在块内部可见。

finally 和 return

try...catch
块无论如何结束,finally子句都执行。包括显示的return方式返回。

下面的示例,在try中有return,在这种情况,在控制返回外部代码之前,finally被执行。

function func() {

try {
return 1;

} catch (e) {
/* ... */
} finally {
alert( 'finally' );
}
}

alert( func() ); // first works alert from finally, and then this one


try…finally

try…finally结构,没有
catch
子句,也有用。当我们不想在这里处理错误时可以应用,但是要确保开始和最终过程被执行。

function func() {
// start doing something that needs completion (like measurements)
try {
// ...
} finally {
// complete that thing even if all dies
}
}


在上面的代码中,try块的错误总会发生,因为没有catch块,但finally在执行流跳出外部之前会执行。

全局错误捕获

环境规范

本节中的信息不是核心JavaScript的一部分。

我们想像在
try...catch
块之外有个致命错误,那么代码会立刻停止。如编程错误或其他更糟糕的事情。

是否有应对此类事件的方法?我们可能想记录错误,向用户显示一些信息(通常他不会看到错误消息)等等。

Javascript规范中没有涉及,但环境通常都提供实现,因为确实有用。如,Node.JS有process.on(“uncaughtException”),在浏览器中可以给window.onerror赋值一个函数。它将在未捕获错误的情况下运行。

语法:

window.onerror = function(message, url, line, col, error) {
// ...
};


message

错误信息

url

发生错误脚本url

line, col

错误发生在代码中行、列数

error

错误对象

<script>
window.onerror = function(message, url, line, col, error) {
alert(`${message}\n At ${line}:${col} of ${url}`);
};

function readData() {
badFunc(); // Whoops, something went wrong!
}

readData();
</script>


全局错误处理
window.error
的角色通常不能恢复脚本执行,在编程错误的情况下是不可能的,但会给开发者发送错误信息。

也有一些web服务为这种情况提供了错误日志记录,如 https://errorception.comhttp://www.muscula.com.

工作流程如下:

注册服务,然后获得一段JS脚本,插入至页面中。

该JS脚本中有自定义的
window.error
函数。

当错误发生时,会给服务器端发送网络请求。

我们可以登录服务的web界面查看错误信息。

总结

结构
try...catch
可以处理运行时错误,字面理解为尝试运行代码,然后捕获可能发生的错误。

语法为:

try {
// run this code
} catch(err) {
// if an error happened, then jump here
// err is the error object
} finally {
// do in any case after try/catch
}


也可能没有
catch
finally
块,所以
try...catch
try...finally
都是有效语法。

错误对象有下面属性:

message
——用户可以理解的错误信息。

name
——错误名称字符串(错误构造函数名称)。

stack
——非标准——发生错误是的堆栈信息。

我们也能通过使用
throw
操作生成自己的错误,技术上,
throw
的参数可以是任意类型,但通常是从内置错误类
Error
继承的对象。后面会介绍扩展错误对象。

再次抛出是错误处理的基本模式:
catch
块通常期望并知道怎样处理特定的错误,所以应该重新抛出未知错误。

即使我们没有使用
try...catch
,大多数环境也支持设置全局的错误处理,捕获所有发生的错误,浏览器内置的是
window.onerror
.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: