Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Dart (3.13)  Module 11 – Asynchronous programming

async, await and asynchronous errors

Write Dart async functions with await and try, run calls in parallel with .wait and Future.wait, and handle the errors of futures nobody awaits.

  • Intermediate
  • 10 minutes
  • Examples run with dart 3.13.5 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 4 of 10 minutes free
Free: An async function runs to its first await, then returns a future · try and catch work across await · Start the calls first, then await them together. With a pass: 2 in-depth parts and 3 interview answers, about 6 minutes.

What you will learn

  • Write async functions that await futures and catch their errors with try and catch
  • Run independent calls in parallel with a record's .wait and with Future.wait
  • Handle a partial failure, a deadline and a retry without losing the results that arrived
  • Prevent lost futures with lint rules, unawaited and a zone's error handler

Before you start

What you’ll learn · Before you start

What you will learn

  • Write async functions that await futures and catch their errors with try and catch
  • Run independent calls in parallel with a record's .wait and with Future.wait
  • Handle a partial failure, a deadline and a retry without losing the results that arrived
  • Prevent lost futures with lint rules, unawaited and a zone's error handler

Before you start

On this page

A checkout page needs a price and a stock count, and each comes from a call that takes 300 ms. Awaited one after the other they cost at least 600 ms; started together, about 300. Either call can also fail, and an error that escapes the wrong line ends the program.

This lesson writes async functions with await, catches asynchronous errors with an ordinary try, and waits for several calls at once, with programs that ran on Dart 3.13.5.

An async function runs to its first await, then returns a future

Mark a function async and it may use await. Its body starts at once, inside the call, like any other function. At its first await it stops and hands its caller a future, and it finishes later, when the awaited future completes:

async_flow.dart: where an async function pauses and resumes Dart · async_flow.dart
// An async function runs synchronously up to its first await, hands its caller a future, and finishes later.
Future<int> fetchPrice(String sku) async {
  print('fetchPrice: starts and runs to its first await');
  await Future.pause(const Duration(milliseconds: 20));
  print('fetchPrice: resumes after the await');
  return 499;
}

Future<void> main() async {
  print('main: calls fetchPrice');
  final pending = fetchPrice('pen');
  print('main: has ${pending.runtimeType}, is Future<int>: ${pending is Future<int>}');
  final price = await pending;
  print('main: price $price');
}

Output

main: calls fetchPrice
fetchPrice: starts and runs to its first await
main: has Future<int>, is Future<int>: true
fetchPrice: resumes after the await
main: price 499

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run async_flow.dart

fetchPrice printed its first line before main got anything back. Then main held a Future<int> that had not completed, and the rest of fetchPrice ran 20 ms later. An await never blocks the thread: it hands control back to the event loop and resumes the function when the future completes. Step through the messages:

Interactive · message by message async_flow.dart, message by message Step through async_flow.dart. Watch where each function pauses at an await and what wakes it up again.
async_flow.dart, message by message, step 6 of 6. Messages so far: main to fetchPrice: fetchPrice('pen'); fetchPrice to event loop: pause 20 ms; fetchPrice to main: Future<int>; main to event loop: await; event loop to fetchPrice: timer event; fetchPrice to main: 499.mainfetchPriceevent loopfetchPrice('pen')pause 20 msFuture<int>awaittimer event499

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. main calls fetchPrice. main prints its first line and calls fetchPrice. An async function starts at once, inside the call, like any other function.
  2. The body runs to its await. fetchPrice prints its first line synchronously. At the await it starts a 20 ms pause timer and stops there.
  3. main gets a future. Stopping at its first await makes fetchPrice return to main with a Future<int> that has not completed yet. main prints that it holds a future.
  4. main awaits the future. main reaches its own await. It pauses too, and the event loop can run other callbacks while both functions wait.
  5. The timer fires. After 20 ms the pause timer's event comes up. fetchPrice resumes on the line after its await and prints that it has resumed.
  6. The future completes. fetchPrice returns 499. That completes the future main is waiting for, so main resumes with 499 and prints the price.

An async function: A function marked async. It may use await, always returns a future, runs synchronously up to its first await and finishes later, when the futures it awaits have completed. always returns a future. return 499 in a function declared Future<int> completes that future with 499, which is what the caller’s await produces.

Checkpoint

Check: Async functions and their first await

  1. Question 1 of 2 In async_flow.dart, which line is printed first after main: calls fetchPrice?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: fetchPrice: starts and runs to its first await

    Calling an async function runs its body at once until the first await; only then does the caller get a future.

  2. Question 2 of 2 An async function is declared Future<int> fetchPrice() async and ends with return 499. What does a call give its caller?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A Future<int> that later completes with 499

    async_flow.dart printed that main held a Future<int>, and await turned it into 499.

try and catch work across await

Asynchronous errors arrive after the call that caused them, but await brings them back to one line: when the awaited future fails, the await throws its error right there. So the try, on, catch and finally of exceptions and errors work unchanged:

await_errors.dart: try, on and finally around an await Dart · await_errors.dart
// An awaited future's error is thrown at the await, so an ordinary try, on, catch and finally handle it.
import 'dart:async';

Future<int> fetchStock(String sku) async {
  await Future.pause(const Duration(milliseconds: 10));
  if (sku == 'ink') throw TimeoutException('stock service did not answer for "$sku"');
  return 12;
}

Future<void> showStock(String sku) async {
  print('$sku: asking');
  try {
    final count = await fetchStock(sku);
    print('$sku: $count in stock');
  } on TimeoutException catch (e) {
    print('$sku: stock unknown (${e.message})');
  } finally {
    print('$sku: spinner stopped');
  }
}

Future<void> main() async {
  await showStock('pen');
  await showStock('ink');
}

Output

pen: asking
pen: 12 in stock
pen: spinner stopped
ink: asking
ink: stock unknown (stock service did not answer for "ink")
ink: spinner stopped

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run await_errors.dart

For pen the stock arrived. For ink, fetchStock failed 10 ms after it was called, the await in showStock threw the TimeoutException, and on TimeoutException handled it; finally stopped the spinner on both paths. That is the main gain over then and catchError: asynchronous errors are handled with the same statements, in the same order, as synchronous ones.

The same holds one level up. main awaited showStock twice, so if showStock itself had thrown, the error would have reached main at that await, and with no try there the program would end with exit code 255.

Checkpoint

Check: try and catch around an await

  1. One question fetchStock('ink') fails 10 ms after it is called. Where does showStock's try catch the TimeoutException?

    Choose one answer.

    Show the answer: One question

    Answer: At the await line, when the awaited future fails

    await_errors.dart printed stock unknown for ink and spinner stopped from finally.

Start the calls first, then await them together

Two awaits in a row wait one after the other: the second call does not start until the first has finished. When the calls are independent, start both and wait for both. Put the futures in a record and call .wait on it; the result is a record of values that a pattern unpacks:

waits.dart: two 300 ms calls, in turn and together Dart · waits.dart
// Two calls of 300 ms each: awaited one after the other, then started together and awaited as a record.
Future<int> fetchPrice(List<String> log) async {
  log.add('price starts');
  await Future.pause(const Duration(milliseconds: 300));
  log.add('price ends');
  return 499;
}

Future<int> fetchStock(List<String> log) async {
  log.add('stock starts');
  await Future.pause(const Duration(milliseconds: 300));
  log.add('stock ends');
  return 12;
}

Future<void> main() async {
  final clock = Stopwatch()..start();
  final one = <String>[];
  final price1 = await fetchPrice(one);
  final stock1 = await fetchStock(one);
  final sequential = clock.elapsedMilliseconds;
  print('one after the other: ${one.join(', ')}');
  print('  price $price1, stock $stock1, at least 600 ms: ${sequential >= 600}');

  clock.reset();
  final both = <String>[];
  final (price2, stock2) = await (fetchPrice(both), fetchStock(both)).wait;
  final parallel = clock.elapsedMilliseconds;
  print('started together: ${both.join(', ')}');
  print('  price $price2, stock $stock2, under 600 ms: ${parallel < 600}');
}

Output

one after the other: price starts, price ends, stock starts, stock ends
  price 499, stock 12, at least 600 ms: true
started together: price starts, stock starts, price ends, stock ends
  price 499, stock 12, under 600 ms: true

Recorded with Dart SDK 3.13.5 (stable) on macos_arm64 on macOS 26 arm64. To run it yourself: dart run waits.dart

The log shows the difference. In turn, the stock call started only after the price call had ended, so the pair took at least 600 ms. Together, both calls were running before either ended, and the pair took under 600 ms, close to the 300 ms of one call. Future.wait does the same for a list of futures of one type.

Three more tools finish the job. A ParallelWaitError reports which calls failed and keeps the values of the rest. timeout gives up waiting after a deadline you choose, and a retry tries again after growing pauses. Lint rules, the unawaited function and a zone: A dart:async context in which code and the callbacks it schedules run. A zone can bring its own handler for asynchronous errors that nothing else handles.’s error handler deal with the futures that nobody awaits.

Key idea

Start independent calls first and await them together with .wait; two awaits in a row run the calls one after the other.

Checkpoint

Check: Waiting for calls together

  1. Question 1 of 2 Which log line order shows two calls running at the same time?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: price starts, stock starts, price ends, stock ends

    waits.dart printed the overlapping order for the record's .wait and took under 600 ms.

  2. Question 2 of 2 What does await (fetchPrice(), fetchStock()).wait produce when both succeed?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A record of the two values, such as (499, 12)

    final (price, stock) = await (...).wait unpacked 499 and 12 in waits.dart.

Partial failures, timeouts and retries, with recorded runs Pro: opens with a Pro, Premium or Ultimate pass

One failed call should not throw away the answers that did arrive. This part handles a failure inside a parallel wait, puts a deadline on a slow call and retries a flaky one with growing pauses, each with a program that ran.

In this part:

  • A ParallelWaitError keeps the values that arrived
  • timeout stops the waiting, not the call
  • Retry with growing pauses, and only for errors worth retrying

3 runnable examples (Dart) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Lost futures, the lint rules that find them and a zone for stray errors Pro: opens with a Pro, Premium or Ultimate pass

A future that nobody awaits takes its error out of reach of every try. This part reproduces the crash such a future causes, runs the two lint rules that find it, and sets up a zone handler for the errors that still slip through.

In this part:

  • A future nobody awaits escapes every try
  • Two lint rules find the missing await
  • runZonedGuarded gives stray errors one handler

1 diagram · 4 runnable examples (Dart, Python) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise

Check the three ideas of the sections above, async functions, a try around an await and parallel waits, in a short session that explains every answer:

Practice that adapts to you

Practise this lesson

7 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 What type does an async function declared to return a String give its caller?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: Future<String>

    The return statement completes the future; await on the future gives the String.

  2. Question 2 of 7 f() is async: it prints A, awaits a pause, prints B. main calls f() without await, then prints C. What is printed?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: A, C, B

    The body runs synchronously to its first await; the rest waits for the awaited future.

  3. Question 3 of 7 When an awaited future completes with an error, where is that error thrown?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: At the await expression, in the async function that awaits it

    await_errors.dart handled the TimeoutException with on TimeoutException around the await.

  4. Question 4 of 7 A spinner must stop whether a stock lookup succeeds or fails. Where does spinner.stop() go in an async function?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: In a finally block of the try that surrounds the await

    await_errors.dart printed spinner stopped for pen and for ink.

  5. Question 5 of 7 A teammate asks why your code uses try and await instead of then and catchError. What is the strongest reason?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: Errors are handled with the same statements and in the same order as synchronous code

    Both styles work. await keeps the order of the code and lets try, on, catch and finally do their usual jobs.

  6. Question 6 of 7 Two independent calls take 300 ms and 500 ms. About how long does awaiting them together with .wait take?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: About 500 ms, the time of the slower one

    In waits.dart two 300 ms calls took under 600 ms together, close to the time of one.

  7. Question 7 of 7 You have a List<Future<int>> of twenty price lookups. Which call waits for all of them?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: Future.wait(lookups)

    Future.wait suits a list of futures of one type; a record's .wait suits a few futures of different types.

Interview questions

Warm-up (fresher): What does an async function return, and when does its body start running?

It returns a future, always: Future<T> for a function that returns a T, and Future<void> when it returns nothing. Its body starts at once, during the call, and runs synchronously up to its first await. There it stops, and the caller receives the future, which has not completed yet. The rest of the body runs later, when the awaited future completes, and the function’s return value then completes the caller’s future. In async_flow.dart, fetchPrice printed its first line before main received the Future<int>.

Three longer questions take these ideas into failures and deadlines:

  1. Mid to senior

    A product page loads price, stock and reviews in parallel with Future.wait. The reviews service fails about 2 % of the time, and each time the whole page shows an error although price and stock arrived. Redesign the waiting so the page shows what arrived, and say what the code should log.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    After a refactor, a command line tool sometimes ends with Unhandled exception and exit code 255 a moment after it prints saved. The trace points at an analytics upload. Find the cause, fix it so a failed upload never ends the command, and stop the same bug from coming back.

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    A payment-status call usually answers in 300 ms but sometimes hangs for 30 seconds. Add a 2-second deadline and up to three attempts with backoff. Say what happens to the calls that hang, the longest a user can wait, and which failures you retry.

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does an async function return, and when does its body run?

    Always a future. Its body starts at once, inside the call, runs synchronously up to its first await and then hands the caller a future that completes when the body finishes.

  2. Does an await block the thread?

    No. It hands control back to the event loop and resumes the function when the awaited future completes.

  3. How do you catch the error of an awaited future?

    With an ordinary try: when the awaited future fails, the await throws its error on that line, so try, on, catch and finally work as they do for synchronous code.

  4. Two independent 300 ms calls: how do you wait for both in about 300 ms instead of 600?

    Start both first, put the futures in a record and await its .wait; the result is a record of values that a pattern unpacks. Two awaits in a row run the calls one after the other. Future.wait does the same for a list of one type.

  5. What happens when an awaited async call throws and no caller up to main catches it?

    The error reaches main at its await, and with no try there the program ends with exit code 255.

Key takeaways

  • An async function runs at once up to its first await, returns a future to its caller and finishes when the awaited future completes.
  • An await throws the error of a future that failed, so try, on, catch and finally handle asynchronous errors like synchronous ones.
  • Two awaits in a row run the calls one after the other; starting both and awaiting a record’s .wait runs them together, in about the time of the slower one.

Terms in this lesson

async function
A function marked async. It may use await, always returns a future, runs synchronously up to its first await and finishes later, when the futures it awaits have completed.
lost future
A future that no code awaits and no handler is attached to, so its result goes nowhere and its error cannot be caught by the code that started it.
zone
A dart:async context in which code and the callbacks it schedules run. A zone can bring its own handler for asynchronous errors that nothing else handles.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.