Futures are one of the very important part of Dart programming language which are used to do asynchronous programming. They are similar to Promises in JavaScript.
Dart is single threaded and executes the code synchronously. Whenever it comes across an asynchronous event such as a future, it will send that event to an event loop. Once the main thread executes all synchronous code and becomes free, then it starts executing the asynchronous callback functions based on their priority (which event happened first).
A future is nothing but a value which is the result of an asynchronous operation. Since the value is not known when we create a future, because it gets executed later, its state will be set to uncompleted. Once the operation is over, its state will be set to completed (these terms are similar to pending and resolved in JavaScript Promises).
example:
void main() {
print('Synchronous code start...');
Future.value('Asynchronous code...').then((value) => print(value));
print('Synchronous code end...');
}
Output:
Synchronous code start...
Synchronous code end...
Asynchronous code...
In the above example, first we have a print statement which gets executed immediately.
Then we have created a future by using the Future.value constructor function. Since this is an async operation, it will be moved to the event pool. The Future.value method completes the future immediately in the event pool, but it do not run the code yet, as there are other lines of code which needs to get executed in the main thread (the last print statement).
After executing the final line of code, which is again a print statement, the main thread becomes free. Dart then checks the event pool for completed events and executes them.
Similar to a Promise, we need to use .then to get the value returned from a future or .catchError to catch errors.
Chaining Futures
Futures can be chained.
void main() {
Future.value('Resolved Value').then((value) {
print(value);
return 'Second Value';
}).then((value) {
print(value);
return 'Third Value';
}).then((value) => print(value));
}
output:
Resolved Value
Second Value
Third Value
Error Handling:
We can use the .catchError block of a future to catch any errors
example:
void main() {
Future.value('Resolved Value').then((value) {
print(value);
return Future.error('Error Message');
}).catchError((error) {
print(error);
});
}
Output:
Resolved Value
Error Message
In this example, we first received the value from a future. We can work with this value and based on our requirements, we can then return an error if required which will be received in the next error handler function. To throw an error, we used Dart’s Future.error function.
Future Constructors
The constructor function which we have used earlier Future.value creates a future which gets completed immediately with the value passed to it. Dart provides different other constructors to handle different scenarios.
Future.delayed
We can pass two arguments to this function. The first argument is duration and the second one is a callback function which gets executed after the specified duration.
Future<String> delayedFuture = Future.delayed(const Duration(seconds: 1), () {
return 'After a second';
});
void main() {
delayedFuture.then((value) => print(value));
}
Future.sync
If you want to execute your future synchronously, you can use the Future.sync function.
example:
void main() {
print('Start');
Future.sync(() => print('Sync'));
print('End');
}
Output:
Start
Sync
End
Here we can see that the Future got executed synchronously.
Future.wait
Many times we may have to work with multiple futures at a time. Suppose, we want to work with two futures. If the second future depends on the data retrieved from the first future, then we need to trigger the second future in the first future’s successful callback function.
If the futures are independent from each other, then we may want to start all of them simultaneously. If we start them separately, then getting the results of all future at a place can be a difficult task to achieve. We can use the Future.wait constructor function to handle this scenario easily.
Future<String> myFuture1 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 1 ');
Future<String> myFuture2 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 2 ');
Future<List<String>> myFutures = Future.wait({myFuture1, myFuture2});
void main() {
myFutures.then((value) => print(value));
}
Output:
[ Success Data 1 , Success Data 2 ]
Future Methods
Earlier we have seen a couple of methods which are available on Futures. One is then and the other one is catchError.
whenComplete
The callback function passed to whenComplete will get executed when the future state becomes complete.
Future<String> delayedFuture = Future.delayed(const Duration(seconds: 1), () {
return 'Success';
});
void main() {
delayedFuture.then((value) {
print(value);
}).catchError((error) {
print(error);
}).whenComplete(() => print('completed'));
}
output:
Success
completed
For futures with errors
Future<String> delayedFuture = Future.delayed(const Duration(seconds: 1), () {
return Future.error('Error');
});
void main() {
delayedFuture.then((value) {
print(value);
}).catchError((error) {
print(error);
}).whenComplete(() => print('completed'));
}
output:
Error
completed
Async – Await in Dart / Flutter
Create futures using async
Previously we used built-in Future class to create futures. Instead of using that we can use Async to create a future.
A function marked with async keyword will automatically returns a future.
example:
void main() {
Future<String> asyncFunction() async {
return 'Async Function';
}
asyncFunction().then((value) => print(value));
}
Output:
Async Function
In this example we created a function asyncFunction and added async keyword before the function body. This makes the function returns a future. We then called the function and used .then to get the value from the future as we did before.
Instead if returning a value, we can also throw an error if required which will be received in the catchError block.
example:
void main() {
Future<String> asyncFunction() async {
throw 'Error message';
}
asyncFunction().then((value) => print(value)).catchError((error) {
print('Catch Error: ' + error); // Catch Error: Error message
});
}
Using futures inside an async function
We can use a function marked with async keyword before its body, not only to create a future but also to handle already created futures. Await is the keyword that is almost always used to do this.
example:
Future<String> myFuture =
Future.delayed(Duration(seconds: 5), () => ' Success Data ');
void main() {
void asyncFunction() async {
var getData = await myFuture;
print(getData); // we get Success Data after 5 secs
}
asyncFunction();
}
In this example, instead of using .then to get the value from the future, we used await inside the async function.
Note that, we can use await ONLY with async. We cannot use await keyword in normal functions.
The async-await syntax is less verbose, more readable and more easier to handle multiple futures .
example:
Future<String> myFuture1 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 1 ');
Future<String> myFuture2 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 2 ');
void main() {
void asyncFunction() async {
var getData1 = await myFuture1;
var getData2 = await myFuture2;
print(getData1);
print(getData2);
}
asyncFunction();
}
Output: (we get after 10 secs)
Success Data 1
Success Data 2
Running Futures Parallelly
When you run the above code, we get the output after 10 secs. That is because, the first await takes 5 secs and then only the second future starts which again takes 5 secs. If the second future depends on the data from the first future, this type of approach will be quite useful.
Instead, if you want to start all futures simultaneously, we can use the Future.wait method.
example:
Future<String> myFuture1 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 1 ');
Future<String> myFuture2 =
Future.delayed(Duration(seconds: 5), () => ' Success Data 2 ');
Future<List<String>> myFutures = Future.wait({myFuture1, myFuture2});
void main() {
void asyncFunction() async {
var getData = await myFutures;
print(getData);
}
asyncFunction();
}
Output: (after 5 secs)
[ Success Data 1 , Success Data 2 ]
Async – Await Error Handling
We can use try and catch method to handle async await errors.
example:
Future<int> myFuture = Future.delayed(Duration(seconds: 5), () {
return Future.error('Error after 5 secs');
});
Future<void> asyncFunction() async {
try {
var val = await myFuture;
print(val);
} catch (error) {
print(error);
}
}
void main() {
asyncFunction();
}
Output (after 5 secs):
Error after 5 secs
Leave A Comment