Dart Lesson 67 of 102 4 min read
async and await in Dart
Learn how async and await work in Dart: write asynchronous code that reads like normal code, return futures, and avoid common mistakes.
On this page
async and await are two keywords that let you work with futures as if the code were synchronous. They do not add new abilities. They make asynchronous code readable.
The two rules #
- Mark a function
asyncto be allowed to useawaitinside it. Anasyncfunction always returns aFuture. - Put
awaitbefore a future to pause the function until that future completes, and get its value.
Future<String> fetchGreeting() async {
await Future.delayed(Duration(seconds: 1));
return 'Namaste'; // automatically wrapped in a Future<String>
}
Future<void> main() async {
print('Loading...');
final greeting = await fetchGreeting();
print(greeting);
}
Loading...
Namaste
Inside fetchGreeting you return a plain String. Because the function is async, the caller receives a Future<String>.
Compared with then #
Future<int> fetchUserId() => Future.delayed(Duration(milliseconds: 300), () => 7);
Future<String> fetchName(int id) =>
Future.delayed(Duration(milliseconds: 300), () => 'User $id');
Future<int> fetchOrderCount(String name) =>
Future.delayed(Duration(milliseconds: 300), () => 12);
// With then: nested and hard to follow.
void withThen() {
fetchUserId().then((id) {
fetchName(id).then((name) {
fetchOrderCount(name).then((count) {
print('$name has $count orders');
});
});
});
}
// With await: reads top to bottom.
Future<void> withAwait() async {
final id = await fetchUserId();
final name = await fetchName(id);
final count = await fetchOrderCount(name);
print('$name has $count orders');
}
Future<void> main() async {
await withAwait();
}
User 7 has 12 orders
What await really does #
await pauses only the function it is in. Control goes back to the caller, which keeps running.
Future<void> task() async {
print('2. task started');
await Future.delayed(Duration(seconds: 1));
print('4. task finished');
}
void main() {
print('1. main started');
task(); // not awaited
print('3. main reached its last line');
}
1. main started
2. task started
3. main reached its last line
4. task finished
task runs synchronously until its first await. Then it steps aside, main continues, and task resumes a second later.
Forgetting await #
Leave out await and you get the future itself, not the value.
Future<int> fetchCount() async => 5;
Future<void> main() async {
var wrong = fetchCount();
var right = await fetchCount();
print(wrong);
print(right);
}
Instance of 'Future<int>'
5
Dart’s type checker usually catches this, because a Future<int> cannot be used as an int. Turn on the unawaited_futures lint to be warned when a future is ignored inside an async function.
Sequential or parallel #
Each await waits for the previous one. That is right when the second call needs the first result. When the calls are independent, start them all first and then wait.
Future<String> fetch(String what) =>
Future.delayed(Duration(seconds: 1), () => '$what loaded');
Future<void> main() async {
var watch = Stopwatch()..start();
// One after another: about 2 seconds.
final a = await fetch('profile');
final b = await fetch('settings');
print('$a, $b in ${watch.elapsed.inSeconds}s');
watch.reset();
// Together: about 1 second.
final (c, d) = await (fetch('profile'), fetch('settings')).wait;
print('$c, $d in ${watch.elapsed.inSeconds}s');
}
profile loaded, settings loaded in 2s
profile loaded, settings loaded in 1s
More on this in running futures in parallel.
await in loops #
Future<String> download(String file) =>
Future.delayed(Duration(milliseconds: 200), () => '$file ok');
Future<void> main() async {
for (final file in ['a.png', 'b.png', 'c.png']) {
print(await download(file)); // one at a time, in order
}
}
a.png ok
b.png ok
c.png ok
Do not use forEach with an async callback. It does not wait for the callbacks, so the code after it runs too early. Use a for-in loop.
Async all the way #
A function that awaits must be async, so its caller must await it, and so on upwards. That is normal. main itself can be async.
Rules of thumb #
- Return type of an async function is
Future<T>, orFuture<void>when there is no result. awaitworks on any future, including ones from libraries.- Use
try/catcharoundawaitto handle failures. See async error handling. - Do not mark a function
asyncif it contains noawait. Just return the future.
Try it yourself #
Write three functions that each wait half a second: login() returns a token, fetchProfile(token) returns a name, and fetchPosts(name) returns a list of titles. Call them in order with await and print how many posts the user has.