前言
Dart 語言提供了多種異步編程方式,比如 Future,比如async / await,再比如 Stream。如何更好地進(jìn)行異步編程,我們來看看官方的指引。
相比 Future,優(yōu)先使用 async / await
異步代碼的可讀性差和難于調(diào)試是臭名昭著的,即便是使用 Future 這樣比較好的抽象方式也是一樣。 async / await 語法改善了可讀性,并且可以在所有 Dart 的控制流的異步代碼中使用。下面是一個(gè)典型的例子。通過 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 方式的話,那代碼簡直是無法直視 —— 一般人要理清這樣的業(yè)務(wù)邏輯非常困難。
// 錯(cuò)誤示例
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
很容易習(xí)慣在有異步操作的函數(shù)定義時(shí)使用 async。但是,在某些情況下卻是多余的。如果移除 async 對函數(shù)的行為沒有影響的話,那么就大膽地移除吧。
// 正確示例
Future<int> fastestBranch(
Future<int> left, Future<int> right) {
return Future.any([left, right]);
}
// 錯(cuò)誤示例
Future<int> fastestBranch(Future<int> left, Future<int> right) async {
return Future.any([left, right]);
}
下面是使用 async 的幾個(gè)場景:
- 在函數(shù)內(nèi)部有使用到
await—— 這是最常見的情況。 - 需要異步返回一個(gè)錯(cuò)誤。使用
async后再拋出錯(cuò)誤會比使用return Future.error(...)更簡潔。 - 如果像用
Future對象包裹一個(gè)返回值,那么使用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
很對異步編程的新手想寫代碼嘗試產(chǎn)生一個(gè) Future 對象,而 Future 的構(gòu)造方法似乎滿足不了他們的需要,然后他們就會使用 Completer 類來做這樣的事情。
// 錯(cuò)誤示例
Future<bool> fileContainsBear(String path) {
var completer = Completer<bool>();
File(path).readAsString().then((contents) {
completer.complete(contents.contains('bear'));
});
return completer.future;
}
Completer適用于兩類底層代碼:新的異步原語和不使用 Future 對象的異步接口。大部分代碼都應(yīng)該使用 async / await 或者 Future.then,這是因?yàn)樗麄兏忧逦?,而且也更容易處理錯(cuò)誤。比如下面的代碼會更加清晰簡潔,個(gè)人來說,會更喜歡 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');
}
當(dāng)處理 FutureOr<T>這種類型時(shí),務(wù)必記得檢查這個(gè)是Future<T>還是對象
FutureOr<T> 聲明的對象可能是 Future<T> 或僅僅是 T 類型的對象。因此,在處理這種類型時(shí),通常需要使用 is 檢查它到底是Future 對象還是普通對象。對于 FutureOr<int>這類特殊的類型來說,使用is int 或 is Future<int>沒什么區(qū)別。但是,如果值的類型是 Object或是一個(gè)使用 Object實(shí)例化的類型參數(shù),那么就有區(qū)別了。這是因?yàn)槿绻褂?is T 判斷的話,Future<T>對象因?yàn)橐彩?Object,結(jié)果會返回 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;
}
}
// 錯(cuò)誤示例
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;
}
}
錯(cuò)誤示例中,如果傳過去的參數(shù)是Future<Object>的話,那么logValue 這個(gè)函數(shù)會將它當(dāng)做沒有使用 Future 包裹的原始對象進(jìn)行處理,結(jié)果導(dǎo)致程序錯(cuò)誤。因此,對 FutureOr<T>是否是異步對象判斷時(shí),應(yīng)當(dāng)先使用 is Future<T>判斷,次序不要弄錯(cuò)了。