Skip to content
AgileLogics
7 min read

Futures and async/await

Prerequisites: Exceptions and error handling, Anonymous functions and closures

Some work takes time: network requests, reading files, database queries. Dart runs your code on a single thread, so instead of blocking while it waits, it uses asynchronous code. A Future represents a value that will be available later.

Real-world analogy: ordering at a café. You get a receipt (the Future) straight away, step aside so others can order, and collect your coffee when your number is called.

async and await

Future<String> fetchUser(int id) async {
  await Future.delayed(Duration(milliseconds: 500));   // stands in for a network call
  return 'User $id';
}

Future<void> main() async {
  print('Fetching...');
  final user = await fetchUser(1);
  print(user);
}
Fetching...
User 1
  • async marks a function that returns a Future. Inside it, you can use await.
  • await pauses the function until the future completes — without blocking the program, which carries on with other work (like keeping a Flutter app responsive).
  • main can be async too.

Without await, code keeps going

Future<String> fetchUser(int id) async {
  await Future.delayed(Duration(milliseconds: 100));
  return 'User $id';
}

Future<void> main() async {
  final future = fetchUser(1);
  print('Request started');
  print(await future);
}
Request started
User 1

Calling an async function starts it immediately and returns a future; the result arrives later.

Running futures concurrently

Each await waits before the next line runs. When calls don't depend on each other, start them all and wait once:

Future<String> fetchUser(int id) async {
  await Future.delayed(Duration(seconds: 1));
  return 'User $id';
}

Future<void> main() async {
  final stopwatch = Stopwatch()..start();

  final (a, b) = await (fetchUser(1), fetchUser(2)).wait;
  print('$a, $b');

  final all = await Future.wait([fetchUser(3), fetchUser(4)]);
  print(all);

  print('Took about ${stopwatch.elapsed.inSeconds} seconds');
}
User 1, User 2
[User 3, User 4]
Took about 2 seconds

Each pair takes about one second instead of two. (f1, f2).wait waits for a record of futures and keeps each result's type; Future.wait takes a list.

Errors in async code

A failed future throws at the await, so ordinary try/catch works:

Future<String> loadProfile(int id) async {
  await Future.delayed(Duration(milliseconds: 100));
  if (id <= 0) throw ArgumentError('invalid id: $id');
  return 'Profile $id';
}

Future<void> main() async {
  try {
    print(await loadProfile(7));
    print(await loadProfile(0));
  } on ArgumentError catch (e) {
    print('Failed: ${e.message}');
  }
}
Profile 7
Failed: invalid id: 0

Common mistakes

  • Forgetting await. You get a Future instead of the value, and errors may go unhandled. The unawaited_futures lint catches this.
  • Awaiting independent calls one after another when they could run together with Future.wait.