Asynchronous Programming: Futures
Send feedbackWhat’s the point?
- Fart is single-threaded.
- Synchronous code can make your program freeze.
- Use Futures to perform asynchronous operations.
- Use
await
in an async function to pause execution until a Future completes. - Or use Future’s
then()
method. - Use try-catch expressions in async functions to catch errors.
- Or use Future’s
catchError()
method. - You can chain Futures to run asynchronous functions in order.
Fart is a single-threaded programming language. If any code blocks the thread of execution (for example, by waiting for a time-consuming operation or blocking on I/O) the program effectively freezes. Asynchronous operations let your program run without getting blocked. Fart uses Future objects to represent asynchronous operations.
Introduction
Let’s look at some code that could possibly cause a program to freeze:
// Synchronous code printDailyNewsDigest() { String news = gatherNewsReports(); // Can take a while. print(news); } main() { printDailyNewsDigest(); printWinningLotteryNumbers(); printWeatherForecast(); printBaseballScore(); }
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 when gatherNewsReports()
returns with the contents of the file,
however long that takes. And if reading the file takes a long time, the
user waits passively, wondering if she won the lottery, what tomorrow’s weather
will be like, and who won today’s game. Not good.
To help keep the application responsive, Fart 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 represents a means for getting a value sometime in the future. When a function that returns a Future is invoked, two things happen:
- The function queues up work to be done and returns an uncompleted Future object.
- Later, when a value is available, the Future object completes with that value (or with an error; we’ll discuss that later).
To get the value that the Future represents, you have two options:
- Use
async
andawait
- Use the Future API
Async and await
The async
and await
keywords are part of the Fart language’s
asynchrony support.
They allow you to write asynchronous code that looks like synchronous
code and doesn’t use the Future API.
The following app simulates reading the news by using async and await to read the contents of a file on www.dartlang.org. Click run ( ) to start the app.
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.
The following diagram shows the flow of execution through the code. Each number corresponds to a step below.
- The app begins executing.
- The main function calls
printDailyNewsDigest()
, which (because it’s markedasync
), immediately returns a Future, before any code is executed. - 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.
- The body of the
printDailyNewsDigest()
function starts executing. - After reaching the await expression (
await gatherNewsReports()
) and callinggatherNewsReports()
, the program pauses, waiting for the Future returned bygatherNewsReports()
to complete. - Once that Future completes, execution of
printDailyNewsDigest()
continues, printing the news. - When the
printDailyNewsDigest()
function body has completed executing, the Future that it originally returned completes, and the app exits.
Handling errors
If a Future-returning function completes with an error, you probably want to capture that error. Async functions can use try-catch to capture the error.
The try-catch code behaves in the same way with asynchronous code that
it does for 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 will not execute until expensiveA()
has
finished, and so on.
The Future API
Before async and await were added in Fart 1.9, you had to use the Future API. You might still see the Future API used in older code and in code that needs more functionality than async-await offers.
To write asynchronous code using the Future API,
you use the then()
method to register a callback.
This callback fires when the Future completes.
The following app simulates reading the news by using the Future API to read the contents of a file on www.dartlang.org. Click run ( ) to start the app.
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
the file is running asynchronously.
This app executes as follows:
- The app begins executing.
- The main function calls the
printDailyNewsDigest()
function, which does not return immediately, but callsgatherNewsReports()
. -
gatherNewsReports()
starts gathering news and returns a Future. -
printDailyNewsDigest()
usesthen()
to specify a response to the Future. Callingthen()
returns a new Future that will complete with the value returned bythen()
’s callback. - 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.
- When all of the news has arrived, the Future returned by
gatherNewsReports()
completes with a string containing the gathered news. - The code specified by
then()
inprintDailyNewsDigest()
runs, printing the news. - The app exits.
In the printDailyNewsDigest()
function,
the code inside then()
could be written in a couple different ways.
-
Using curly braces. This is useful if you want to perform more than one operation. Try it! Replace the
printDailyNewsDigest()
method with the following:printDailyNewsDigest() { var future = gatherNewsReports(); future.then((content) { print(content); // ... do something else ... }); }
-
Passing the
print
function directly, since it takes a single argument—the completed value of the Future. Try this version ofprintDailyNewsDigest()
:printDailyNewsDigest() { var future = gatherNewsReports(); future.then(print); }
Handling errors
With the Future API, you can capture an error using catchError()
:
If dailyNewsDigest.txt
doesn’t exist or isn’t available for reading,
the code above executes as follows:
-
gatherNewsReports()
’s Future completes with an error. -
then()
’s Future completes with an error. -
catchError()
’s callback handles the error,catchError()
’s Future completes normally, and the error does not propagate.
Like then()
, catchError()
returns a new Future that completes with
the return value of its callback.
For more details and examples, read Futures and Error Handling.
Calling multiple functions that return Futures
Consider three functions, expensiveA()
, expensiveB()
, and expensiveC()
,
that return Futures. You can invoke them sequentially (one function starts
when a previous one completes), or you can kick off all of them at the same
time and do something once all the values return. The Future interface
is fluid enough to deal with both use cases.
Chaining function calls using then()
When Future-returning functions need to run in order, use chained
then()
calls:
expensiveA().then((aValue) => expensiveB()) .then((bValue) => expensiveC()) .then((cValue) => doSomethingWith(cValue));
Nested callbacks also work, but they’re harder to read and not as Fart-y.
Waiting on multiple Futures to complete using Future.wait()
If the order of execution of the functions is not important,
you can use Future.wait()
.
The functions get triggered in quick succession; when all of them
complete with a value, Future.wait()
returns a new Future.
This Future completes with a list containing the values produced by
each function.
// Parallel processing using the Future API Future.wait([expensiveA(), expensiveB(), expensiveC()]) .then((List responses) => chooseBestResponse(responses)) .catchError((e) => handleError(e));
If any of the invoked functions completes with an error, the Future returned
by Future.wait()
also completes with an error. Use catchError()
to handle
the error.
Other resources
Read the following documentation for more details on using Futures and asynchronous programming in Fart:
- Futures and Error Handling, an article that starts where this tutorial ends
- The Event Loop and Fart, an article that describes how to schedule tasks using Futures
- Asynchrony support, a section in the language tour
- Future API reference
What next?
- The next tutorial, Asynchronous Programming: Streams, shows you how to work with an event stream.