DMarlon Studio

Lesson 14 · 14 of 20

Future.wait and async errors · Dart

Run several requests in parallel with Future.wait in Dart and handle their errors with try/catch, timeout and onError, with output that never changes.

Dart · Future.wait and async errors in DMarlon Studio: the example's code and its output in the terminal

Awaited one after another, three calls of 60, 10 and 30 ms would take 100 ms. `Future.wait` starts them all at once and waits for them to finish, so it takes as long as the slowest. The key to a predictable result is that the list of results follows the order of the input list, not the order in which they finish: that is why Stocks always prints [35, 63, 21].

fetchStock throws a StateError for codes starting with X. Inside an async function, an exception makes its Future complete with an error, and await rethrows that error in whoever is waiting. So an ordinary try/catch with on StateError around await Future.wait(...) is all it takes.

To wait for futures of different types at the same time, Dart 3 lets you group them in a record and call .wait: (Future.value('Mug'), fetchStock(...)).wait returns a (String, int) record that is destructured into name and stock, each with its own type.

`timeout` turns a call that is too slow into a TimeoutException, which comes from dart:async: SLOW-API takes 300 ms and is abandoned after 50. And when a failure is not serious, `onError` replaces the error with a fallback value: the unknown product gives 0 instead of an exception.

Code · main.dart

import 'dart:async';Future<int> fetchStock(String sku, int ms) async {  await Future.delayed(Duration(milliseconds: ms)); // simulated network  if (sku.startsWith('X')) throw StateError('unknown product $sku');  return sku.length * 7;}Future<void> main() async {  // In parallel: results come back in list order, not in finishing order.  final stocks = await Future.wait(      [fetchStock('MUG-1', 60), fetchStock('TSHIRT-22', 10), fetchStock('CAP', 30)]);  print('Stocks: $stocks');  // If one fails, await throws and an ordinary try/catch handles it.  try {    await Future.wait([fetchStock('MUG-1', 10), fetchStock('X-404', 5)]);  } on StateError catch (e) {    print('Failed: ${e.message}');  }  // A record of futures of different types, awaited together.  final (name, stock) = await (Future.value('Mug'), fetchStock('MUG-1', 20)).wait;  print('$name: $stock in stock');  // A timeout turns a call that is too slow into an error.  try {    await fetchStock('SLOW-API', 300).timeout(const Duration(milliseconds: 50));  } on TimeoutException {    print('Gave up after 50 ms');  }  // Or recover with a fallback value instead of an exception.  print('Fallback: ${await fetchStock('X-1', 5).onError((e, _) => 0)}');}

Output · dart run main.dart

Stocks: [35, 63, 21]
Failed: unknown product X-404
Mug: 35 in stock
Gave up after 50 ms
Fallback: 0

Key points

  • Future.wait runs in parallel and returns the results in order.
  • An error in a Future resurfaces at the await: catch it with try/catch.
  • (f1, f2).wait waits for futures of different types.
  • timeout puts a time limit on a call.
  • onError returns a fallback value when something fails.

Exercise

Add a third element to the record, fetchStock('CAP', 15), destructure it into a caps variable and print all three values on one line.