# 流程控制和错误处理

# 相关关键字

  • if and else
  • for loops
  • while and do-while loops
  • break and continue
  • switch and case
  • assert

# if-else

if (isRaining()) {
  you.bringRainCoat();
} else if (isSnowing()) {
  you.wearJacket();
} else {
  car.putTopDown();
}

Dart中if只能接收布尔表达式,不能接受其它类型值(这一点和JavaScript不同)。

# for loop

var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
  message.write('!');
}

这里输出的是0和1,是符合预期的,避免了js中的一个陷阱:

var callbacks = [];
for (var i = 0; i < 2; i++) {
  callbacks.add(() => print(i));
}
callbacks.forEach((c) => c());

作为对比,由于js中var没有块级作用域,所以输出的都是2(通过把var改成let,可以解决):

var callbacks = [];
for (var i = 0; i < 2; i++) {
  console.log(i)
  callbacks.push(() => console.log(i));
}
callbacks.forEach((c) => c());

如果某个object是可以迭代的,并且你使用时不关心当前迭代计数,那么使用forEach是一个好选择:

candidates.forEach((candidate) => candidate.interview());

可迭代的类支持for-in来迭代值,比如SETList

var collection = [0, 1, 2];
for (var x in collection) {
  print(x); // 0 1 2
}

# While and do-while

while循环,在执行前判断是否可循环:

while (!isDone()) {
  doSomething();
}

do-while循环,在循环执行后判断循环条件:

// 至少执行一次
do {
  printLine();
} while (!atEndOfPage());

# Break and continue

使用break关键字跳出循环:

// 执行break后循环终止。
while (true) {
  if (shutDownRequested()) break;
  processIncomingRequests();
}

使用continue跳过本次循环,进入下次循环:

for (int i = 0; i < candidates.length; i++) {
  var candidate = candidates[i];
  if (candidate.yearsExperience < 5) {
    continue;
  }
  candidate.interview();
}

对于可迭代类,可以替换成一下的写法:

// where是对于candidates的过滤,只保留大于等于5的
// 过滤完的类,循环执行interview方法
candidates
    .where((c) => c.yearsExperience >= 5)
    .forEach((c) => c.interview());
// 执行结果和上面的写法一样,但是这里.where和.forEach一共执行了两次循环。比上面的写法多一次循环。
// 这里也能稍微看出命令式编程(for循环)和函数式编程的区别。

# Switch and case

Dart中的switch语句通过使用==比较整数,字符串或编译时常量。比较的object必须都是同一个类的实例(而不是其任何子类型),并且该类不能覆盖==操作符。 当没有case子句匹配时,使用default子句执行代码:

var command = 'OPEN';
switch (command) {
  case 'CLOSED':
    executeClosed();
    break;
  case 'PENDING':
    executePending();
    break;
  case 'APPROVED':
    executeApproved();
    break;
  case 'DENIED':
    executeDenied();
    break;
  case 'OPEN':
    executeOpen();
    break;
  default:
    executeUnknown();
}

每个case都要有对应的break,否则会报错,但是如果case的内容为空,可以省略break

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

// 空case省略break,这样当command == 'CLOSED'时,会执行'NOW_CLOSED'的代码
var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

如果你真的想要执行完某个case之后,再执行下面某个case,可以通过label标记来实现:

// case 'CLOSED'执行完后根据标记nowClosed,继续执行case 'NOW_CLOSED'的内容。
// 由于case 'CLOSED'执行后必然会执行case 'NOW_CLOSED',所以case 'CLOSED'没有break
var command = 'CLOSED';
switch (command) {
  case 'CLOSED':
    executeClosed();
    continue nowClosed;
  // Continues executing at the nowClosed label.

  nowClosed:
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

case子句可以具有局部变量,这些变量仅在该子句的范围内可见:

switch (command) {
  case 'CLOSED':
    final a = 123;
    print(a);
    continue nowClosed;
  case '123':
    break;
  nowClosed:
  case 'NOW_CLOSED':
	  // print(a);
		// 解开上面的注释会报错。
    break;
}

# Assert

在开发期间,使用assert语句 —— assert(condition,optionalMessage);

如果布尔条件为假,则中断正常执行并抛出exception

示例如下:

// Make sure the variable has a non-null value.
assert(text != null);

// Make sure the value is less than 100.
assert(number < 100);

// Make sure this is an https URL.
assert(urlString.startsWith('https'));

要将消息附加到断言,只需要添加一个字符串作为断言的第二个参数:

assert(urlString.startsWith('https'),
    'URL ($urlString) should start with "https".');

assertions何时起作用取决于您使用的工具和框架,例如flutter会在 debug mode (opens new window)时执行assertions。 在生产代码中,assertions将被忽略,并且断言条件将不会执行。

# Exceptions

Dart可以抛出并捕获exceptionexception是指示发生意外事件的错误。如果未捕获exception,c程序会抛出exception,并且通常会终止程序执行。 与Java相比,Dart的所有异常都是未经检查的exception。方法不会声明它们可能抛出的exception,并且您不需要捕获任何异常。 Dart提供了ExceptionError类型,以及许多预定义的子类型。并且你也可以定义自己的exception。此外,Dart程序可以抛出任何非nullobject - 不仅仅是ExceptionError对象 - 作为exception

# Throw

抛出错误:

throw FormatException('Expected at least 1 section');

抛出其他类型:

throw 'Out of llamas!';

因为抛出exception是一个表达式,所以可以在=>语句中以及允许表达式的任何其他地方抛出exception:

void distanceTo(Point other) => throw UnimplementedError();

# Catch

捕获exception会阻止exception传播(除非你重新抛出exception)。捕获exception使你有机会处理它:

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  buyMoreLlamas();
}

要处理可能抛出多种类型exception的代码,可以指定多个catch子句。与抛出对象的类型匹配的第一个catch子句会处理exception。如果catch子句未指定类型,则该子句可以处理任何类型的抛出object

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // 指定的exception捕获
  buyMoreLlamas();
} on Exception catch (e) {
  // 任何的exception捕获
  print('Unknown exception: $e');
} catch (e) {
  // 捕获所有抛出的object,无论是否是exception类实例。
  print('Something really unknown: $e');
}

你可以为catch()指定一个或两个参数。第一个是抛出的exception,第二个是堆栈跟踪(stack trace):

try {
  // ···
} on Exception catch (e) {
  print('Exception details:\n $e');
} catch (e, s) {
  print('Exception details:\n $e');
  print('Stack trace:\n $s');
}

要处理exception,同时允许它传播,可以使用rethrow关键字:

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Runtime error
  } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print('main() finished handling ${e.runtimeType}.');
  }
}

# Finally

如果想要确保某些代码运行,无论是否抛出exception,请使用finally子句。如果没有catch子句与异常匹配,则在finally子句运行后传播exception

try {
  breedMoreLlamas();
} finally {
  // cleanLlamaStalls总会执行,即使抛出了exception
  cleanLlamaStalls();
}

finally子句在任何匹配的catch子句之后运行:

try {
  breedMoreLlamas();
} catch (e) {
  print('Error: $e'); // 先处理exception
} finally {
  cleanLlamaStalls(); // 然后执行cleanLlamaStalls
}

也就是说,无论你是否catchexceptionfinally子句都会执行。