Dart 异步编程注意事项
前言
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 方式的话,那代码简直是无法直视 —— 一般人要理清这样的业务逻辑非常困难。
// 错误示例
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<T>这种类型时,务必记得检查这个是Future<T>还是对象
FutureOr<T>
声明的对象可能是 Future<T>
或仅仅是 T
类型的对象。因此,在处理这种类型时,通常需要使用 is
检查它到底是Future 对象还是普通对象。对于 FutureOr<int>
这类特殊的类型来说,使用is int
或 is 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>
判断,次序不要弄错了。