Asynchronous programming: futures & async-await

What's the point?

  • Dart code runs in a single “thread” of execution.
  • Code that blocks the thread of execution can make your program freeze.
  • Future objects (futures) represent the results of asynchronous operations — processing or I/O to be completed later.
  • To suspend execution until a future completes, use await in an async function.
  • To catch errors, use try-catch expressions in async functions.
  • To run code concurrently, create an isolate (or for a web app, a worker).

Dart code runs in a single “thread” of execution. If Dart code blocks — for example, by performing a long-running calculation or waiting for I/O — the entire program freezes.

Asynchronous operations let your program complete other work while waiting for an operation to finish. Dart uses Future objects (futures) to represent the results of asynchronous operations. To work with futures, you can use either async and await or the Future API.


Let’s look at some code that might cause a program to freeze:

// Synchronous code
void printDailyNewsDigest() {
  var newsDigest = gatherNewsReports(); // Can take a while.

main() {

Our program gathers the news of the day, prints it, and then prints a bunch of other items of interest to the user:

<gathered news goes here>
Winning lotto numbers: [23, 63, 87, 26, 2]
Tomorrow's forecast: 70F, sunny.
Baseball score: Red Sox 10, Yankees 0

Our code is problematic: since gatherNewsReports() blocks, the remaining code runs only after gatherNewsReports() returns with the contents of the file, however long that takes. If reading the file takes a long time, the user has to wait, wondering if they won the lottery, what tomorrow’s weather will be, and who won today’s game.

To help keep the application responsive, Dart library authors use an asynchronous model when defining functions that do potentially expensive work. Such functions return their value using a future.

What is a future?

A future is a Future<T> object, which represents an asynchronous operation that produces a result of type T. If the result isn’t a usable value, then the future’s type is Future<void>. When a function that returns a future is invoked, two things happen:

  1. The function queues up work to be done and returns an uncompleted Future object.
  2. Later, when the operation is finished, the Future object completes with a value or with an error.

When writing code that depends on a future, you have two options:

  • Use async and await
  • Use the Future API

Async and await

The async and await keywords are part of the Dart language’s asynchrony support. They allow you to write asynchronous code that looks like synchronous code and doesn’t use the Future API. An async function is one that has the async keyword before its body. The await keyword works only in async functions.

The following app simulates reading the news by using async and await to read the contents of a file on this site. Click run to start the app. Or open a DartPad window containing the app, run the app, and click CONSOLE to see the app’s output.

Notice that printDailyNewsDigest() is the first function called, but the news is the last thing to print, even though the file contains only a single line. This is because the code that reads and prints the file is running asynchronously.

In this example, the printDailyNewsDigest() function calls gatherNewsReports(), which is non-blocking. Calling gatherNewsReports() queues up the work to be done but doesn’t stop the rest of the code from executing. The program prints the lottery numbers, the forecast, and the baseball score; when gatherNewsReports() finishes gathering news, the program prints. If gatherNewsReports() takes a little while to complete its work, no great harm is done: the user gets to read other things before the daily news digest is printed.

Note the return types. The return type of gatherNewsReports() is Future<String>, which means that it returns a future that completes with a string value. The printDailyNewsDigest() function, which doesn’t return a value, has the return type Future<void>.

The following diagram shows the flow of execution through the code. Each number corresponds to a step below.

diagram showing flow of control through the main() and printDailyNewsDigest functions

  1. The app begins executing.
  2. The main() function calls the async function printDailyNewsDigest(), which begins executing synchronously.
  3. printDailyNewsDigest() uses await to call the function gatherNewsReports(), which begins executing.
  4. The gatherNewsReports() function returns an uncompleted future (an instance of Future<String>).
  5. Because printDailyNewsDigest() is an async function and is awaiting a value, it pauses its execution and returns an uncompleted future (in this case, an instance of Future<void>) to its caller (main()).
  6. The remaining print functions execute. Because they’re synchronous, each function executes fully before moving on to the next print function. For example, the winning lottery numbers are all printed before the weather forecast is printed.
  7. When main() has finished executing, the asynchronous functions can resume execution. First, the future returned by gatherNewsReports() completes. Then printDailyNewsDigest() continues executing, printing the news.
  8. When the printDailyNewsDigest() function body finishes executing, the future that it originally returned completes, and the app exits.

Note that an async function starts executing right away (synchronously). The function suspends execution and returns an uncompleted future when it reaches the first occurrence of any of the following:

  • The function’s first await expression (after the function gets the uncompleted future from that expression).
  • Any return statement in the function.
  • The end of the function body.

Handling errors

If a Future-returning function completes with an error, you probably want to capture that error. Async functions can handle errors using try-catch:

Future<void> printDailyNewsDigest() async {
  try {
    var newsDigest = await gatherNewsReports();
  } catch (e) {
    // Handle error...

The try-catch code behaves in the same way with asynchronous code as it does with synchronous code: if the code within the try block throws an exception, the code inside the catch clause executes.

Sequential processing

You can use multiple await expressions to ensure that each statement completes before executing the next statement:

// Sequential processing using async and await.
main() async {
  await expensiveA();
  await expensiveB();
  doSomethingWith(await expensiveC());

The expensiveB() function doesn’t execute until expensiveA() has finished, and so on.

Other resources

Read the following documentation for more details on using futures and asynchronous programming in Dart:

What next?