Dart 异步编程注意事项

戳这里了解《Flutter入门与实战》专栏,持续更新、系统学习!

前言

Dart 语言提供了多种异步编程方式,比如 Future,比如async / await,再比如 Stream。如何更好地进行异步编程,我们来看看官方的指引。

相比 Future,优先使用 async / await

异步代码的可读性差和难于调试是臭名昭著的,即便是使用 Future 这样比较好的抽象方式也是一样。 async / await 语法改善了可读性,并且可以在所有 Dart 的控制流的异步代码中使用。下面是一个典型的例子。通过 async / await 语法糖,代码逻辑非常清晰易懂。

// 正确示例
Future<int> countActivePlayers(String teamName) async {
  try {
    var team = await downloadTeam(teamName);
    if (team == null) return 0;

    var players = await team.roster;
    return players.where((player) => player.isActive).length;
  } catch (e) {
    log.error(e);
    return 0;
  }
}

如果使用原始的 Future 方式的话,那代码简直是无法直视 —— 一般人要理清这样的业务逻辑非常困难。
image.png

// 错误示例
Future<int> countActivePlayers(String teamName) {
  return downloadTeam(teamName).then((team) {
    if (team == null) return Future.value(0);

    return team.roster.then((players) {
      return players.where((player) => player.isActive).length;
    });
  }).catchError((e) {
    log.error(e);
    return 0;
  });
}

不要使用没必要的 async

很容易习惯在有异步操作的函数定义时使用 async。但是,在某些情况下却是多余的。如果移除 async 对函数的行为没有影响的话,那么就大胆地移除吧。

// 正确示例
Future<int> fastestBranch(
    Future<int> left, Future<int> right) {
  return Future.any([left, right]);
}

// 错误示例
Future<int> fastestBranch(Future<int> left, Future<int> right) async {
  return Future.any([left, right]);
}

下面是使用 async 的几个场景:

  • 在函数内部有使用到 await —— 这是最常见的情况。
  • 需要异步返回一个错误。使用 async 后再抛出错误会比使用 return Future.error(...) 更简洁。
  • 如果像用 Future 对象包裹一个返回值,那么使用 async 会比 Future.value(...) 更加简洁。
// 正确示例
Future<void> usesAwait(Future<String> later) async {
  print(await later);
}

Future<void> asyncError() async {
  throw 'Error!';
}

Future<void> asyncValue() async => 'value';

避免直接使用 Completer

很对异步编程的新手想写代码尝试产生一个 Future 对象,而 Future 的构造方法似乎满足不了他们的需要,然后他们就会使用 Completer 类来做这样的事情。

// 错误示例
Future<bool> fileContainsBear(String path) {
  var completer = Completer<bool>();

  File(path).readAsString().then((contents) {
    completer.complete(contents.contains('bear'));
  });

  return completer.future;
}

Completer适用于两类底层代码:新的异步原语和不使用 Future 对象的异步接口。大部分代码都应该使用 async / await 或者 Future.then,这是因为他们更加清晰,而且也更容易处理错误。比如下面的代码会更加清晰简洁,个人来说,会更喜欢 async /await的形式。

// 正确示例1:使用 Future.then
Future<bool> fileContainsBear(String path) {
  return File(path).readAsString().then((contents) {
    return contents.contains('bear');
  });
}

// 正确示例2:使用 async / await
Future<bool> fileContainsBear(String path) async {
  var contents = await File(path).readAsString();
  return contents.contains('bear');
}

当处理 FutureOr这种类型时,务必记得检查这个是Future还是对象

FutureOr<T> 声明的对象可能是 Future<T> 或仅仅是 T 类型的对象。因此,在处理这种类型时,通常需要使用 is 检查它到底是Future 对象还是普通对象。对于 FutureOr<int>这类特殊的类型来说,使用is intis Future<int>没什么区别。但是,如果值的类型是 Object或是一个使用 Object实例化的类型参数,那么就有区别了。这是因为如果使用 is T 判断的话,Future<T>对象因为也是 Object,结果会返回 true,比如下面的例子:

// 正确示例
Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is Future<T>) {
    var result = await value;
    print(result);
    return result;
  } else {
    print(value);
    return value;
  }
}

// 错误示例
Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is T) {
    print(value);
    return value;
  } else {
    var result = await value;
    print(result);
    return result;
  }
}

错误示例中,如果传过去的参数是 Future<Object>的话,那么logValue 这个函数会将它当做没有使用 Future 包裹的原始对象进行处理,结果导致程序错误。因此,对 FutureOr<T>是否是异步对象判断时,应当先使用 is Future<T>判断,次序不要弄错了。

猜你喜欢

转载自blog.csdn.net/shuijian00/article/details/124672500