异常处理

Dart代码可以抛出(throw)和捕获(catch)异常(Exception)。异常代表了程序的某种错误,也就是一些意想不到的事情。引发异常的代码分支及其上下文,即测试中的负向路径。

Dart的函数或方法不会声明它将抛出哪些异常; Dart也不强制要求捕获异常。如果异常发生了却不被处理,抛出异常的 isolate 将被挂起,且通常会被终止。第7章将详细讨论 isolate,这里将其视为一种内存独立的线程(如 main 函数所在的 main isolate)即可。

抛出异常 (trhow

Dart程序可以抛出(trhow)什么?答案是除了null外的任何东西。

// ch0208_1.dart
void main() {
  throw 'something bad happened';
  // Unhandled exception:
  // something bad happened
}

此例使用throw 关键字,抛出了一个字符串。

ExceptionError

Dart提供了 ExceptionError 类及相关子类(第4章将介绍类),官方建议我们在产品代码中抛出 ExceptionError 的实现类。例如下面这些类实现了Exception

  • AnalysisException
  • DeferredLoadException
  • FileSystemException
  • FormatException
  • IOException
  • IsolateSpawnException
  • PathException
  • RemoteException
  • TimeoutException

我们也可以创建自己的异常类,并实现(implementsExceptionError

// ch0208_2.dart
void main() {
  throw FooException();
}

class FooException implements Exception {}

ExceptionError 的区别:

  • Exception旨在向外传递一个失败信息(例如超时异常 TimeoutException),以便通过编程的方式解决;它应该包含有用的信息,且希望被捕获。

  • Error代表了程序员引起的错误(例如断言错误 AssertionError), 它理应被避免,且通常暗示了一个bug,我们不应捕获它。

捕获异常

捕获指定类型的异常

使用 try...on...catch 语法捕获异常。将可能抛出异常的代码放在try{}代码块中,然后使用on关键字捕获特定类型的异常。

// ch0208_3.dart
import 'dart:io';

void main() {
  var str = 'dart';
  try {
    int.parse(str);
  } on FormatException {
    stderr.writeln('FormatException: Cannot parse "$str" as an integer.');
  }
}

此例试图将字符串"dart"转换为一个整数,引发了 FormatException,随后被捕获。

捕获异常信息

使用catch关键字捕获异常信息(即异常类型的一个实例)。

// ch0208_4.dart
void main() {
  try {
    int.parse('dart');
  } on FormatException catch (ex) {
    print(ex.message); // Output: Invalid radix-10 number
  } 
}

catch() 还有另一个可选参数,表示异常堆栈。

// ch0208_5.dart
void main() {
  try {
    int.parse('dart');
  } on FormatException catch (e, s) {
    print(e);
    print(s);
  }
}

重新抛出异常

使用 rethrow关键字重新抛出异常,且保留原来的堆栈信息。

// ch0208_6.dart
void main() {
  try {
    parseInt('dart');
  } catch (_, s) {// 2
    print(s); // 2a
  }
}

int parseInt(String str) {
  try {
    return int.parse(str);
  } on FormatException catch (_, s) {
    print(s); // 1a
    rethrow; // 1
  }
}
  1. 使用rethow关键字将捕获到的FormatException再次抛出;
  2. 这里仅用了catch (没有使用on指定异常类型),表示捕获所有类型的异常。

捕获多种类型的异常

使用多个catch子句来分别处理不同类型的异常。

//ch0206_7.dart
void main() {
  print(safeDivide('8', '2'));
  print(safeDivide('8', '0'));
  print(safeDivide('hello', 'dart'));
}

(String? err, int) safeDivide(String a, String b) {
  try {
    return (null, int.parse(a) ~/ int.parse(b));
  } on FormatException catch (e) {
    return ('FormatException: ${e.source}', 0);
  } on UnsupportedError catch (e) { // IntegerDivisionByZeroException
    return ('UnsupportedError: ${e.message}', 0);
  } on Exception catch (e) {
    return ('unknown exception: $e', 0);
  }
}

finally

无论try 代码块内是否发生了异常, finally子句都将被执行。finally子句通常用来关闭资源(如打开的系统文件)。

// ch0208_8.dart
void main() {
  try {
    print(2 ~/ 0);
  } on UnsupportedError {
    print('oops');
  } finally {
    print('done');
  }
  // Output:
  // oops
  // done
}

参考资料