How to bring a little asynchronous programming to Dart with futures
Asynchronous programming is a form of parallel programming that allows a unit of work to run separately from the primary application thread. When the work is complete, it notifies the main thread (as well as whether the work was completed or failed). There are numerous benefits to using it, such as improved application performance and enhanced responsiveness.
Problem
Dart code runs in a single thread of execution, so if it’s blocked the entire program freezes.
Example:
//..var data = fetchDataFromSomeApi(); // Takes time while fetching datadoStuffWithData(data); // Manipulate the datadoOtherStuff(); // Do other stuff unrelated to the data//..
Since fetchDataFromSomeApi()
blocks, the remaining code runs only after fetchDataFromSomeApi()
returns with the data, however long that takes.
This code runs line by line causing the code to halt and doOtherStuff()
to run after the data fetching operation is finished (which is probably not what you want).
Solution
Asynchronous operations let your program complete other work while waiting for other operation to finish.
Same example as above, but with an asynchronous approach:
fetchDataFromSomeApi().then((data) {
print('Fetched data');
doStuffWithData(data);
});print('Working on other stuff...');
doOtherStuff();// Output:
// Working on other stuff...
// Fetched data
fetchDataFromSomeApi
is non-blocking, meaning it lets other code run while fetching data. Thats why the top level print
statement runs before the print
statement in the callback function.
Futures
A future represents a computation that doesn’t complete immediately. Where a normal function returns the result, an asynchronous function returns a future, which will eventually contain the result. The future will tell you when the result is ready.
Future
objects (futures) represent the results of asynchronous operations — processing or I/O to be completed later.
We can create a future simply like this:
Future future = Future();
let’s define a function called f
:
String f() => 'work is done!';
and pass it to the future:
Future<String> future = Future(f);
Notice that the future takes the same type of the function f
return type String
.
For the purposes of this tutorial we passed a function that just returns a string. This creates a future containing the result of calling f
asynchronously with Timer.run
.
If the result of executing f
throws, the returned future is completed with the error.
If the returned value is itself a Future
, completion of the created future will wait until the returned future completes, and will then complete with the same result.
If a non-future value is returned, the returned future is completed with that value.
then
let’s call then
on the future and pass a function that takes the output of the asynchronous operation as an argument
future.then((String val) => print(val)); // work is done
we can simplify it by passing the print
function only because it takes a string
future.then(print); // work is done
Error handling
To catch errors, use try-catch expressions in async functions (or use catchError()
).
catchError
let’s imagine that our future throws an error at some point:
Future future = Future(() => throw Error());
If we call then
on the future without handling the error, it will throw the error and stop the execution of our program:
future.then(print); // Error: Error: Instance of 'Error'
Let’s define a function that takes an error and handles it:
void handleError(err) {
print(‘$err was handled’);
}
then append catchError()
to the future and pass handleError
:
future.then(print).catchError(handleError); // Error: Error: Instance of 'Error' was handled
This way the error is handled and the program keeps executing.
Async, Await
To suspend execution until a future completes, use await
in an async function (or use then()
).
To use the await
keyword, it has to be inside an async
function like this:
main() async {
Future future = Future(() => ‘work is done’);
String res = await future;
print(res); // work is done
}
Notice that the main
function is marked with the async
keyword.
Any function marked with async
is called asynchronously.
When we call a future with the await
keyword, the function execution is halted until the future
returns a value or throws an error.
We can handle future errors using a try
…catch
block:
main() async {
Future future = Future(() => throw Error());
try {
print(await future);
} catch (e) {
print(‘$e was handled’); // Instance of 'Error' was handled
}
}
Conclusion
- 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 (or usethen()
). - To catch errors, use try-catch expressions in async functions (or use
catchError()
).
If you feel that I have missed something here please let me know, or if you have any question at all you can reach me on twitter.