• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

8.1 错误处理,try..catch

JS 教程 tiantian 429次浏览 0个评论 扫描二维码

不管我们的编程技术如何厉害,我们编写的脚本还是可能出错。它们可能是由于我们的错误引发的,一个无法处理的用户输入,一个错误的服务器响应,或者其他无法完全预料的原因。

一般地,一个脚本在遇到错误时会理解停止,并打印错误信息到控制台。

但是,存在一个语法结构try..catch可以允许脚本来“catch”错误,使得脚本不停止,而是执行某些处理。

try…catch 语法

那个try..catch结构具有两个主要的块:trycatch

try {

  // code...

} catch (err) {

  // error handling

}

它的工作过程:

  1. 首先,位于try {...}内的代码被执行。
  2. 如果不存在错误,那么catch(err)就被忽略:执行流会到达try的结束,然后就跳过catch
  3. 如果发生错误,那么try执行流被停止,控制流到达catch(err)的开头。那个err变量包括一个错误对象,里面包含了错误的详细细节。

8.1 错误处理,try..catch

所以,位于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 是同步的

如果一个异常发生于“计划好”的代码中,比如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

让我们来看看实际编程中的try..catch

正如我们所知,JavaScript 支持JSON.parse(str) 方法来读取 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 方法,toJSON 章节。

如果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操作符可以产生一个错误。

语法为:

throw <error object>

技术上,我们可以使用任何值作为一个错误对象。甚至可以是一个原始类型值,比如一个数字或者一个字符串,但是最好使用对象,如果该对象具有namemessage属性会更加合适,这样可以保持与内建错误对象的兼容性。

JavaScript 对于标准错误都具有很多内建构造器:ErrorSyntaxErrorReferenceErrorTypeError和其他构造器。我们同样可以利用它们来创建错误对象。

语法为:

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属性的情况可以被当作一个语法错误,假设那个用户必须具有一个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 {...}块中发生某些意想不到的错误?比如变量未定义或者其他错误等,而不仅仅是那个数据错误。

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
  // (no JSON Error actually)
}

当然,任何事都可能发生。程序员同样会犯错。即使是在被百万用户使用的开源工具类。比如著名的ssh心跳事件。

在我们的示例中,try..catch用来扑获数据错误的错误。但是,根据规范,catch会扑获所有从try中抛出的错误。此处,我们获得了一个无知的错误,但是却依然显示相同的JSON Error信息。这是误导信息,而且使得那个代码更加难以调试。

幸运的是,我们可以发现实际的错误,比如从它的的name属性:

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

规则很简单:

Catch 应该只处理已知的错误,并且重新抛出所有其他的错误。

这个重抛技术的详细解释如下:

  1. 扑获所有错误。
  2. catch(err) {...}块中,我们分析那个错误对象err
  3. 如果我们无法处理那个错误,我们就重新抛出那个错误throw err

在下面的示例代码中,我们可以使用rethrowing,这样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的错误中恢复过来,而且可以被其他的外部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.

如果存在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' );
}

这个代码具有两种执行流:

  1. 如果发生了错误,那么就是try -> catch -> finally.
  2. 如果没有错误,那么就是try -> finally.

那个finally经常用来清理某些在try..catch之前而获得的某些资源。

举例,我们希望测量一个 Fibonacci 函数fib(n)的运行时间。自然地,我们可以在开始前进行计时,在结束后进行统计。但是,如果在执行过程中发生错误呢?在实际中,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` );

你可以通过分别传入一个正确地或者错误的参数来验证。

换句话说,此处存在两种退出函数的方式:一种是return,另一种是throw。那个finally块可以正确处理它们。

变量位于try..catch..finally内部

请注意,上述的resultdiff变量定义在try..catch之前。

否则的话,如果let定义于{...}块内部,它们它就只能在该块内访问。

finallyreturn

那个finally块可以正确处理从try..catch中的退出。包含一个明确的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
 }
}

在上述示例中,如果有错误,总是会被外部扑获,因为没有catch块。但是finally会在执行流跳出之前被执行。

全局扑获

环境相关

本章节的这部分内容不是 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(); // Whoops, something went wrong!
  }

  readData();
</script>

那个全局处理器window.onerror的处理逻辑通常而言都不是去恢复那个脚本的执行——在出现编程错误的情况下,恢复脚本执行是不可能完成的,而是将错误信息发送给开发人员。

同样的目的,存在 web 服务提供了错误日志的功能。比如:https://errorception.com 和 http://www.muscula.com

它们的工作过程:

  1. 我们在服务端进行注册,并获得相应的 JS 片段或者 URL,并插入到页面中。
  2. 那个 JS 脚本具有一个定制化的 window.onerror函数。
  3. 当错误发生时,它发送一个相应的请求到服务器端。
  4. 我们可以登录那个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..catchtry..finally都是合法的。

错误对象具有下列属性:

  • message —— 人类可读的错误信息。
  • name —— 字符串形式的错误名称(错误构造器名称)
  • stack(不标准) —— 错误发生时的调用栈。

通过使用throw操作符,我们可以创建自己的错误对象。技术上,throw函数的参数可以是任意值,但是通常而言都是一个错误对象,该对象应该继承自内建的Error类。更多关于错误的扩展在下一章节讲解。

重新抛出异常是处理错误的一个基本模式:一个catch块通常可以预知错误的发生并知道如何处理特定类型的错误,所以,它应该重新抛出它无法处理的错误。

即使我们没有try..catch,绝大多数环境还允许设置一个全局的错误处理器,用来扑获所有遗漏的错误。在浏览器中,它处理器为window.onerror

任务


Finally 或者 just the code ?

比较下来的两个代码片段.

  1. 第一个使用finallytry..catch之后执行代码:
try {
  work work
} catch (e) {
  handle errors
} finally {
  cleanup the working space
}
  1. 第二个将清理代码放在try..catch之后:
try {
  work work
} catch (e) {
  handle errors
}

cleanup the working space

不管是否有错误发生,我们都需要一个清理过程。

此处使用finally有什么好处吗?还是说两个代码片段具有等效功能?


天天编码 , 版权所有丨本文标题:8.1 错误处理,try..catch
转载请保留页面地址:http://www.tiantianbianma.com/try-catch.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址