Asynchrony
DO
DO test for Future<T>
when disambiguating a FutureOr<T>
whose type argument could be Object
.
Before you can do anything useful with a FutureOr<T>
, you typically need to do an is
check to see if you have a Future<T>
or a bare T
. If the type argument is some specific type as in FutureOr<int>
, it doesn’t matter which test you use, is int
or is Future<int>
. Either works because those two types are disjoint.
However, if the value type is Object
or a type parameter that could possibly be instantiated with Object
, then the two branches overlap. Future<Object>
itself implements Object
, so is Object
or is T
where T
is some type parameter that could be instantiated with Object
returns true even when the object is a future. Instead, explicitly test for the Future
case:
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 as T;
}
}
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;
}
}
In the bad example, if you pass it a Future<Object>
, it incorrectly treats it like a bare, synchronous value.
PREFER
PREFER async/await over using raw futures.
Asynchronous code is notoriously hard to read and debug, even when using a nice abstraction like futures. The async
/await
syntax improves readability and lets you use all of the Dart control flow structures within your async code.
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<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;
});
}
CONSIDER
CONSIDER using higher-order methods to transform a stream.
This parallels the above suggestion on iterables. Streams support many of the same methods and also handle things like transmitting errors, closing, etc. correctly
AVOID
AVOID using Completer directly.
Many people new to asynchronous programming want to write code that produces a future. The constructors in Future don’t seem to fit their need so they eventually find the Completer class and use that.
Future<bool> fileContainsBear(String path) {
var completer = Completer<bool>();
File(path).readAsString().then((contents) {
completer.complete(contents.contains('bear'));
});
return completer.future;
}
Completer is needed for two kinds of low-level code: new asynchronous primitives, and interfacing with asynchronous code that doesn’t use futures. Most other code should use async/await or Future.then()
, because they’re clearer and make error handling easier.
Future<bool> fileContainsBear(String path) {
return File(path).readAsString().then((contents) {
return contents.contains('bear');
});
}
Future<bool> fileContainsBear(String path) async {
var contents = await File(path).readAsString();
return contents.contains('bear');
}
DON'T
DON’T use async
when it has no useful effect.
It’s easy to get in the habit of using async
on any function that does anything related to asynchrony. But in some cases, it’s extraneous. If you can omit the async
without changing the behavior of the function, do so.
Future afterTwoThings(Future first, Future second) {
return Future.wait([first, second]);
}
Future afterTwoThings(Future first, Future second) async {
return Future.wait([first, second]);
}
Cases where async
is useful include:
-
You are using
await
. (This is the obvious one.) -
You are returning an error asynchronously.
async
and thenthrow
is shorter thanreturn Future.error(...)
. -
You are returning a value and you want it implicitly wrapped in a future.
async
is shorter thanFuture.value(...)
.
Future usesAwait(Future later) async {
print(await later);
}
Future asyncError() async {
throw 'Error!';
}
Future asyncValue() async => 'value';