background
For a long time no more text, in order to avoid the majority of friends have I disappeared the illusion, so and water a text, can help a is a. I wanted to write my own articles, but I found the translation was good. Here is the original text in English.
The paper
final myFuture = http.get("https://example.com");
Copy the code
As in the code above, many Dart asynchronous apis are returned Futures. Future is also one of the most fundamental concepts in Dart asynchronous programming. In general, a Future in Dart is much like a Future or promise in any other programming language.
This article will focus on the concepts behind Future and how to use them. We will also cover the FutureBuilder control in Flutter, which helps you asynchronously update the Flutter UI based on Future state.
Thanks to Dart features like async-await, we may never use the Future API directly. But it’s also hard to avoid coming across a Future in Dart code, as you might want to create a Future or read some future-related code.
How to Understand Future
We can easily think of data as a gift. Now a friend is going to give us a gift box, and as soon as it’s ready, the process begins. After a period of time, we need to open the mystery box. The gift inside may be intact or broken, which means that when a Future is completed, the corresponding result may be the data we expected, or it may be a mistake.
We can summarize this process into three states:
- Unfinished: The gift box is wrapped.
- We are done and get the corresponding value: the box is opened and our data is ready.
- It’s done but there was an error: the box was opened but the gift was damaged.
For the most part, we do some processing around these three states. When we receive a Future, we wait until we open the box, and then we decide what to do, for example, when we receive a value normally, or what to do when something goes wrong. We often see 1-2-3:
When it comes to Future, we can’t help but mention event-loop (shown below, which you can also learn about in the video series above). The Future is just an API that makes it easier to use event-loop.
The Dart code we wrote is executed by a single thread. While our application is running, this thread keeps running and running, and then it keeps picking up events from the Event Queue and processing them.
To better explain event-loop and Future, let’s look at a simple example.
Let’s say we want to implement a download function, and when the user clicks a button, the application automatically downloads an image. , we can simply implement it with RaisedButton:
RaisedButton(
onPressed: () {
final myFuture = http.get('https://my.image.url');
myFuture.then((resp) {
setImage(resp);
});
},
child: Text('Click me! '),Copy the code
Let’s figure out the process together. First, the button click event is triggered. The Event Loop catches the click Event and then calls the click listener (which is onPressed passed in the RaisedButton constructor). Our onPressed makes an HTTP request (http.get()) using the HTTP library, and the request returns a Future(myFuture).
Now we’ve got our gift box, myFuture. Now the gift box is packed. To listen for callbacks to open gift boxes, we use then().
Once we packed the gift box, we had to wait. If other events enter the Event-loop during that time, the user does something else, and the event-loop stays running while your box is still there.
Eventually, the image data is downloaded, and the HTTP library tells us “Great, I’ve got the Future”. It then puts the data into the gift box and opens the gift box, which triggers our callback.
The code snippet in then() is now executed and the image is displayed to the user.
No matter what other tasks were going on or coming in, our code never touched the Event-loop directly. This process does not need to be concerned about what other tasks are going on or what other events are coming in. All we need to do is get the Future from the HTTP library and tell the program what to do when Futuer is done.
In real code, we also care about errors. We’ll get to that later.
Now let’s take a closer look at the FutureAPI, some of which we just saw.
First question, how do we get an instance of a Future? For the most part, we don’t create futures directly. Because most common asynchronous programming tasks already have libraries that can generate futures for us.
For example, the network request returns a Future:
final myFuture = http.get('http://example.com');
Copy the code
Get a shared preferences and return a Future:
final myFuture = SharedPreferences.getInstance();
Copy the code
Of course, we can also create a Future through its constructor.
Future constructor
The simplest Future constructor is Future(), which takes a function as an argument and returns a Future of the same type as the value returned by the function. After a while the function will execute asynchronously, and the Future will return the value of the function when it completes. Look at the Future() example:
void main() {
final myFuture = Future(() {
return 12;
});
}
Copy the code
Let’s add some print statements to make the asynchronous part more obvious:
void main() {
final myFuture = Future(() {
print('Creating the future.'); // Prints second.
return 12;
});
print('Done with main().'); // Prints first.
}
Copy the code
If we run this code on DartPad, the entire main function ends before the function passed to the Future() constructor ends. This is because the Future() constructor happens to return an unfinished Future first. Which means, “This is a box. You need to hold it now, and later I’ll execute your function and put some data in it.”
Done with main().
Creating the future.
Copy the code
The other constructor is future.value (), which handles values that you already know the Future will return. This constructor is useful when we build services that use caching. Sometimes we already hold the value we need, so we can just return:
final myFuture = Future.value(12);
Copy the code
Future.value() also has an opposing constructor that returns an error on completion. It’s future.error (), and it works in much the same way, but this constructor hosts an error object and an optional StackTrace:
final myFuture = Future.error(ArgumentError.notNull('input'));
Copy the code
The most convenient constructor is probably future.delay (). It is the same as Future(), except that it waits a specified amount of time before executing the passed function and then completing the Future.
One use scenario for future.delay () is when we need the mock network service during testing. This future.delay () is useful if we make sure the load indicator displays correctly:
final myFuture = Future.delayed(
const Duration(seconds: 5), () = >12,);Copy the code
The use of the Future
Now that we have a basic understanding of Future, let’s learn how to use it. As we said earlier, using a Future basically revolves around three states: incomplete, completed and got the corresponding value, and completed but got an error.
The following code creates a Future using future.delay (), which returns 100 after 3s.
void main() {
Future.delayed(
const Duration(seconds: 3), () = >100,);print('Waiting for a value... ');
}
Copy the code
When this code executes, main() executes from top to bottom, creating a Future and printing Waiting for a value… At this time, Future is not finished. It won’t be finished for another three seconds.
To use the value returned by the Future, we can use then(). We can register a callback with then() to retrieve the data when the Future is completed. We pass a function to then() that takes only one argument of the same type as the return value of the Future. Once the Future returns and returns data, this function is called and passes the corresponding data:
void main() {
Future.delayed(
const Duration(seconds: 3), () = >100,
).then((value) {
print('The value is $value.'); // Prints later, after 3 seconds.
});
print('Waiting for a value... '); // Prints first.
}
Copy the code
Let’s look at the output log:
Waiting for a value... (3 seconds pass until callback executes)
The value is 100.
Copy the code
In addition to executing our code, then() itself returns its own Future, just as the function we provide returns. So if we need to make a series of asynchronous calls, we can choose to chain them, although their return types are different:
_fetchNameForId(12)
.then((name) => _fetchCountForName(name))
.then((count) => print('The count is $count.'));
Copy the code
Going back to our first example, what happens if an initialized Future completes without corresponding data — that is, what if something goes wrong? The then() method requires a value. At this point we need to register another callback to handle the error case.
The answer is to use [catchError ()] (https://api.dart.dev/stable/2.8.2/dart-async/Future/catchError.html). It is the same as then(), except catchError() does not pass data and will be called if an error occurs during Future execution. Just like then(), catchError() itself returns a Future of its own, so we can build a chain of calls to THEN () and catchError() that wait for each other.
Note: If you use async-await in your code, we don’t need to use then() and catchError(). Because we can get the corresponding value directly with await, we can use try-catch-finally to handle errors. For more information, see the section on asynchronous support in the official Dart documentation.
The following example shows how to use catchError() to handle errors in Futuer:
void main() {
Future.delayed(
Duration(seconds: 3), () = >throw 'Error! '.// Complete with an error.
).then((value) {
print(value);
}).catchError((err) {
print('Caught $err'); // Handle the error.
});
print('Waiting for a value... ');
}
Copy the code
We can even give catchError() a test function that tests the error before the callback is called. In this way, we can have multiple catchError() functions, each checking for a different type of error. The following example shows how to use a detection function to test for errors. The argument test in catchError() is optional.
void main() {
Future.delayed(
Duration(seconds: 3), () = >throw 'Error! ',
).then((value) {
print(value);
}).catchError((err) {
print('Caught $err');
}, test: (err) { // Optional test parameter.
return err is String;
});
print('Waiting for a value... ');
}
Copy the code
Now that you’ve seen the article, hopefully you’ve understood the three states of the Future, and you’ve understood how the three states are represented in the code. In the above example, there are three pieces:
- The first block creates an unfinished
Future
. - when
Future
Execute complete and return corresponding data, callthen()
The callback in. - when
Future
Execution completed but an error occurred and calledcatchError()
The callback in.
There’s another method you might also want to use :whenComplete(). Like the method name, this method is called when the Future completes, regardless of whether the Future returns data or throws an error.
This is a bit like finally in try-catch-finally. Whether the code executes correctly or if something goes wrong, it will be executed.
Use Future in Flutter
We’ve been talking about how to create a Future, and how to use the data in a Future. Now we are going to talk about how to practice Flutter.
Let’s say we have a web service, and the web service returns JSON data, and we want to show that data. We can of course use the StatefulWidget, then create the Future, and call setState() based on the Future’s execution, all of which we have to control manually.
Of course we can also use FutureBuilder. This is a control within the Flutter SDK. We pass a Future and a Builder function, and when the Future completes, it automatically refactors its child controls.
FutureBuilder calls the Builder function, which takes two parameters, context and snapshot, which is the state of the current Future.
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Use a FutureBuilder.
return FutureBuilder<String>( future: _fetchNetworkData(), builder: (context, snapshot) {}, ); }}Copy the code
We can check the snapshot to see if Future has an error:
return FutureBuilder<String>(
future: _fetchNetworkData(5),
builder: (context, snapshot) {
if (snapshot.hasError) {
// Future completed with an error.
return Text(
'There was an error',); }throw UnimplementedError("Case not handled yet"); });Copy the code
We can also check hasData to see if data is returned:
return FutureBuilder<String>(
future: _fetchNetworkData(5),
builder: (context, snapshot) {
if (snapshot.hasError) {
// Future completed with an error.
return Text(
'There was an error',); }else if (snapshot.hasData) {
// Future completed with a value.
return Text(
json.decode(snapshot.data)['field']); }throw UnimplementedError("Case not handled yet"); });Copy the code
If neither hasError nor hasData is true, then we know that the Future is still executing and we need to wait. We can also print some other information:
return FutureBuilder<String>(
future: _fetchNetworkData(5),
builder: (context, snapshot) {
if (snapshot.hasError) {
// Future completed with an error.
return Text(
'There was an error',); }else if (snapshot.hasData) {
// Future completed with a value.
return Text(
json.decode(snapshot.data)['field']); }else {
// Uncompleted.
return Text(
'No value yet! ',); }});Copy the code
Even in the Flutter code we can see how the three states are represented.
conclusion
This article explained how a Future is rendered and how to create a Future using the Future and FutureBuilder apis and use the data in the Future.
If you want to learn more about how to use futures, test your understanding of futures by running sample code and interactive exercises — go to Codelab and practice Futures,aysnc,await.