不管你多么的精通编程,有时我们的脚本总还是会有一些缺点。可能是由于我们的编写出错,或是与预期不同的用户输入,或是缺点的的做事端返回或者是其他总总不同的缘故原由。
常日,一段代码会在出错的时候“去世掉”(停滞实行)并在掌握台将非常打印出来。
但是有一种更为合理的语法构造 try..catch,它会在捕捉到非常的同时不会使得代码停滞实行而是可以做一些更为合理的操作。
“try..catch” 语法
try..catch 构造由两部分组成:try 和 catch:
try { // 代码...} catch (err) { // 非常处理}
它按照以下步骤实行:
首先,实行 try {…} 里面的代码。如果实行过程中没有非常,那么忽略 catch(err) 里面的代码,try 里面的代码实行完之后跳出该代码块。如果实行过程中发生非常,掌握流就到了 catch(err) 的开头。变量 err(可以取其他任何的名称)是一个包含了非常信息的工具。以是,发生在 try {…} 代码块的非常不会使代码停滞实行:我们可以在 catch 里面处理非常。
让我们来看更多的例子。
没有非常的例子:
try { alert('Start of try runs'); // ...这里没有非常 alert('End of try runs'); } catch(err) { alert('Catch is ignored, because there are no errors'); }alert(\公众...Then the execution continues\"大众);
包含非常的例子:
try { alert('Start of try runs'); // (1) <-- lalala; // 非常,变量未定义!
alert('End of try (never reached)'); // (2)} catch(err) { alert(`Error has occured!`); // (3) <--}alert(\"大众...Then the execution continues\"大众);
try..catch only works for runtime errors
要使得 try..catch 能事情,代码必须是可实行的,换句话说,它必须是有效的 JavaScript 代码。
如果代码包含语法缺点,那么 try..catch 不能正常事情,例如含有未闭合的花括号:
try { {{{{{{{{{{{{} catch(e) { alert(\"大众The engine can't understand this code, it's invalid\公众);}
JavaScript 引擎读取然后实行代码。发生在读取代码阶段的非常被称为 “parse-time” 非常,它们不会被 try..catch 覆盖到(包括那之间的代码)。这是由于引擎读不懂这段代码。
以是,try..catch 只能处理有效代码之中的非常。这类非常被称为 “runtime errors”,有时候也称为 “exceptions”。
try..catch works synchronously
如果一个非常是发生在操持中将要实行的代码中,例如在setTimeout中,那么 try..catch 不能捕捉到:
try { setTimeout(function() { noSuchVariable; // 代码在这里停滞实行 }, 1000);} catch (e) { alert( \公众won't work\"大众 );}
由于 try..catch 包裹了操持要实行的 setTimeout 函数。但是函数本身要稍后才能实行,这时引擎已经离开了 try..catch 构造。
要捕捉到操持中将要实行的函数中的非常,那么 try..catch 必须在这个函数之中:
setTimeout(function() { try { noSuchVariable; // try..catch 处理非常!
} catch (e) { alert( \"大众error is caught here!\"大众 ); }}, 1000);Error 工具
当一个非常发生之后,JavaScript 天生一个包含非常细节的工具。这个工具会作为一个参数通报给 catch:
try { // ...} catch(err) { // “非常工具”,可以用其他参数名代替 err // ...}
对付所有内置的非常,catch 代码块捕捉到的相应的非常的工具都有两个属性:
name : 非常名称,对付一个未定义的变量,名称是 “ReferenceError”
message : 非常详情的笔墨描述。
还有很多非标准的属性在绝大多数环境中可用。个中利用最广泛并且被广泛支持的是:
stack : 当前的调用栈:用于调试的,一个包含引发非常的嵌套调用序列的字符串。
例如:
try { lalala; // 非常,变量未定义!
} catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala 未定义 alert(err.stack); // ReferenceError: lalala 在... 中未定义 // 可以完全的显示一个非常 // 可以转化成 \公众name: message\"大众 形式的字符串 alert(err); // ReferenceError: lalala 未定义}
利用 “try..catch”
让我们一起探究一下真实利用场景中 try..catch 的利用。
正如我们所知,JavaScript 支持 JSON.parse(str) 方法来解析 JSON 编码的值。
常日,它被用来解析从网络,从做事器或是从其他来源收到的数据。
我们收到数据后,像下面这样调用 JSON.parse:
let json = '{\公众name\"大众:\"大众John\"大众, \公众age\"大众: 30}'; // 来自做事器的数据let user = JSON.parse(json); // 将文本表示转化成 JS 工具// 现在 user 是一个解析自 json 字符串的有自己属性的工具alert( user.name ); // Johnalert( user.age ); // 30
如果 json 格式缺点,JSON.parse 就会报错,代码就会停滞实行。
得到报错之后我们就该当满意了吗?当然不!
如果这样做,当拿到的数据出错,用户就不会知道(除非他们打开开拓者掌握台)。代码实行失落败却没有提示信息会导致糟糕的用户体验。
让我们来用 try..catch 来处理这个缺点:
let json = \"大众{ bad json }\"大众;try { let user = JSON.parse(json); // <-- 当这里抛出非常... alert( user.name ); // 不事情} catch (e) { // ...跳到这里连续实行 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 }'; // 不完全的数据try { let user = JSON.parse(json); // <-- 不抛出非常 alert( user.name ); // 没有 name!} catch (e) { alert( \"大众doesn't execute\"大众 );}
这里 JSON.parse 正常实行,但是短缺 name 属性对我们来说确实是个非常。
为了统一的非常处理,我们会利用 throw 运算符。
“Throw” 运算符throw 运算符天生非常工具。
语法如下:
throw <error object>
技能上讲,我们可以利用任何东西来作为一个非常工具。乃至可以是根本类型,比如数字或者字符串。但是更好的办法是用工具,尤其是有 name 和 message 属性的工具(某种程度上和内置的非常有可比性)。
JavaScript 有很多标准非常的内置的布局器:Error、 SyntaxError、ReferenceError、TypeError 和其他的。我们也可以用他们来创建非常工具。
他们的语法是:
let error = new Error(message);// 或者let error = new SyntaxError(message);let error = new ReferenceError(message);// ...
对付内置的非常工具(不是对付其他的工具,而是对付非常工具),name 属性刚好是布局器的名字。message 则来自于参数。
例如:
let error = new Error(\"大众Things happen o_O\"大众);alert(error.name); // Erroralert(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 属性,在我们看来,该属性的缺失落也可以看作语法问题。
以是,让我们抛出这个非常。
let json = '{ \公众age\"大众: 30 }'; // 不完全的数据try { let user = JSON.parse(json); // <-- 没有非常 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 操作符天生了包含着我们所给的 message 的 SyntaxError,就犹如 JavaScript 自己天生的一样。try 里面的代码实行停滞,掌握权转交到 catch 代码块。
现在 catch 代码块成为了处理包括 JSON.parse 在内和其他所有非常的地方。
再次抛出非常
上面的例子中,我们用 try..catch 处理没有被精确返回的数据,但是也有可能在 try {…} 代码块内发生另一个预见之外的非常,例如变量未定义或者其他不是返回的数据禁绝确的非常。
例如:
let json = '{ \公众age\"大众: 30 }'; // 不完全的数据try { user = JSON.parse(json); // <-- 忘了在 user 前加 \"大众let\"大众 // ...} catch(err) { alert(\"大众JSON Error: \公众 + err); // JSON Error: ReferenceError: user is not defined // ( 实际上并没有 JSON Error)}
当然,统统皆有可能。程序员也是会犯错的。纵然是一些开源的被数百万人用了几十年的项目 —— 一个严重的 bug 由于他引发的严重的黑客事宜被创造(比如发生在 ssh 工具上的黑客事宜)。
对我们来说,try..catch 是用来捕捉“数据缺点”的非常,但是 catch 本身会捕捉到所有来自于 try 的非常。这里,我们碰着了预见之外的缺点,但是仍旧抛出了 “JSON Error” 的信息,这是禁绝确的,同时也会让我们的代码变得更难调试。
幸运的是,我们可以通过其他办法找出这个非常,例如通过它的 name 属性:
try { user = { /.../ };} catch(e) { alert(e.name); // \"大众ReferenceError\"大众 for accessing an undefined variable}
规则很大略:
catch 该当只捕获已知的非常,而重新抛出其他的非常。
“rethrowing” 技能可以被更详细的理解为:
捕获全部非常。在 catch(err) {…} 代码块,我们剖析非常工具 err。如果我们不知道如何处理它,那我们就做 throw err 操作。不才面的代码中,为了达到只在 catch 代码块处理 SyntaxError 的目的,我们利用重新抛出的方法:
let json = '{ \公众age\"大众: 30 }'; // 不完全的数据try { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError(\公众Incomplete data: no name\"大众); } blabla(); // 预见之外的非常 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(); // 非常!
} catch (e) { // ... if (e.name != 'SyntaxError') { throw e; // 重新抛出(不知道如何处理它) } }}try { readData();} catch (e) { alert( \公众External catch got: \"大众 + e ); // 捕获到!
}
例子中的 readData 只能处理 SyntaxError,而外层的 try..catch 能够处理所有的非常。
try..catch..finally然而,这并不是全部。
try..catch 还有其余的语法:finally
如果它有被利用,那么,所有条件下都会实行:
try 之后,如果没有非常。catch 之后,如果没有非常。该扩展语法如下所示:
try { ... 考试测验实行的代码 ...} catch(e) { ... 非常处理 ...} finally { ... 终极会实行的代码 ...}
试试运行这段代码:
try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE();} catch (e) { alert( 'catch' );} finally { alert( 'finally' );}
这段代码有两种实行办法:
如果对付 “Make an error?” 你的回答是 “Yes”,那么实行 try -> catch -> finally。如果你的回答是 “No”,那么实行 try -> finally。finally 的语法常日用在:我们在 try..catch 之前开始一个操作,不管在该代码块中实行的结果若何,我们都想结束的时候实行某个操作。
比如,天生斐波那契数的函数 fib(n) 的实行韶光,常日,我们在开始和结束的时候丈量。但是,如果该函数在被调用的过程中发生非常,就如实行下面的代码就会返回负数或者非整数的非常。
任何情形下,finally 代码块便是一个很好的结束丈量的地方。
这里,不管前面的代码精确实行,或者抛出非常,finally 都担保了精确的韶光丈量。
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` );
你可以通过后面的不同的输入来考验上面代码的实行:先在 prompt 弹框中先输入 35 —— 它会正常实行,try 代码实行后实行 finally 里面的代码。然后再输入 -1,会立即捕获一个非常,实行韶光将会是 0ms。两次的丈量结果都是精确的。
换句话说,有两种办法退出这个函数的实行:return 或是 throw,finally 语法都能处理。
Variables are local inside try..catch..finally
请把稳:上面代码中的 result 和 diff 变量,都须要在 try..catch 之前声明。
否则,如果用 let 在 {…} 代码块里声明,那么只能在该代码块访问到。
finally and return
finally 语法支持任何的结束 try..catch 实行的办法,包括明确的 return。
下面便是 try 代码块包含 return 的例子。在代码实行的掌握权转移到外部代码之前,finally 代码块会被实行。
function func() { try { return 1; } catch (e) { / ... / } finally { alert( 'finally' ); }}alert( func() ); // 先 alert \公众finally\"大众 里面的内容,再实行这里try..finally
try..finally 构造也很有用,当我们希望确保代码实行完成不想在这里处理非常时,我们会利用这种构造。
function func() { // 开始做须要被完成的操作(比如丈量) try { // ... } finally { // 完成前面要做的事情,纵然 try 里面实行失落败 }}
上面的代码中,由于没有 catch,try 代码块中的非常会跳出这块代码的实行。但是,在跳出之前 finally 里面的代码会被实行。
全局 catchEnvironment-specific
这个部分的内容并不是 JavaScript 核心的一部分。
设想一下,try..catch 之外涌现了一个严重的非常,代码停滞实行,可能是由于编程非常或者其他更严重的非常。
那么,有没办法来应对这种情形呢?我们希望记录这个非常,给用户一些提示信息(常日,用户是看不到提示信息的),或者做一些其他操作。
虽然没有这方面的规范,但是代码的实行环境一样平常会供应这种机制,由于这真的很有用。例如,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(); // 哦,出问题了!
} readData();</script>
window.onerror 的目的不是去处理全体代码的实行中的所有非常 —— 这险些是不可能的,这只是为了给开拓者供应非常信息。
也有针对这种情形供应非常日志的 web 做事,比如 https://errorception.com 或者 http://www.muscula.com。
它们会这样运行:
我们注册这个做事,拿到一段 JS 代码(或者代码的 URL),然后插入到页面中。这段 JS 代码会有一个客户真个 window.onerror 函数。发生非常时,它会发送一个非常干系的网络要求到做事供应方。我们只要登录做事方供应方的网络接口就可以看到这些非常。总结try..catch 构造许可我们处理实行时的非常,它许可我们考试测验实行代码,并且捕获实行过程中可能发生的非常。
语法如下:
try { // 实行此处代码} catch(err) { // 如果发生非常,跳到这里 // err 是一个非常工具} finally { // 不管 try/catch 若何都会实行}
可能会没有 catch 代码块,或者没有 finally 代码块。以是 try..catch 或者 try..finally 都是可用的。
非常工具包含下列属性:
message —— 我们能阅读的非常提示信息。name —— 非常名称(非常工具的布局函数的名称)。stack (没有标准) —— 非常发生时的调用栈。我们也可以通过利用 throw 运算符来天生自定义的非常。技能上来讲,throw 的参数没有限定,但是常日它是一个继续自内置的 Error 类的非常工具。更对关于非常的扩展,请看下个章节。
重新抛出非常,是一种非常处理的基本模式:catch 代码块常日处理某种已知的特定类型的非常,以是它该当抛出其他未知类型的非常。
纵然我们没有利用 try..catch,绝大多数实行环境许可我们设置全局的非常处理机制来捕获涌现的非常。浏览器中,便是 window.onerror。