Let's change the code from the previous section into async, as follows:
import 'dart:io'; main() { File file = new File("data.txt"); file.open().then(processFile); } processFile(RandomAccessFile file) { file.length().then((int length) { file.read(length).then(readFile).whenComplete(() { file.close(); }); }); } readFile(List<int> content) { String contentAsString = new String.fromCharCodes(content); print("Content: $contentAsString"); }
As you can see, the Future class is a proxy for an initially unknown result and returns a value instead of calling a callback function. Future
can be created by itself or with Completer
. Different ways of creating Future
must be used in different cases. The separation of concerns between Future
and Completer
can be very useful. On one hand, we can give Future
to any number of consumers to observe the resolution independently; on the other hand, Completer
can be given to any number of producers and Future
will be resolved by the one that resolves it first. Future
represents the eventual value that is returned from the callback handler, and it can be in one of the following states:
Future
is waiting for the result. The result field holds a single-linked list of Future
listeners.Future
class comes in the pending complete or chained state when it is completed or chained to another Future
class with a success or an error. It will display an error if you try to complete it again.A consumer can register callbacks to handle the value or error of the Future
class. I slightly changed our example to manage exceptions and deliberately used the wrong filename here to throw an exception:
//… main() { File file = new File("data1.txt"); file.open().then(processFile).catchError((error, stackTrace) { print("Catched error is $error $stackTrace"); }, test:(error) { return error is FileSystemException; }).whenComplete((){ print("File closed"); }); } //…
In the preceding code, we added the catchError
method to catch errors. Pay attention to the optional test
parameter of the catchError
method. This parameter is the function that is called first if Future
completes with an error, so you have a chance to check if the instance of an error must be handled in the catchError
method. If optional test parameter is omitted, it defaults to a function that always returns true. If the optional test
parameter returns true
, the function, specified as the first parameter of catchError
, is called with the error and possibly stack trace, and the returned Future is completed with the result of the call of this function. The resulting exceptions will look like this:
Catched error is FileSystemException: Cannot open file, path = 'data1.txt' (OS Error: The system cannot find the file specified. , errno = 2) #0 _File.open.<anonymous closure> (dart:io/file_impl.dart:349) #1 _RootZone.runUnary (dart:async/zone.dart:1082) //… File closed
If the optional test
parameter returns false
, the exception is not handled by the catchError
method and the returned Future
class is completed with the same error and stack trace.
Last but not least, the Future
class has the whenComplete
method. This method has one parameter that is considered a function, which is always called in the end regardless of the future result (refer to the last statement in the preceding code).
Now when we are finished with definitions, let's discuss the different factory constructors of Future
.
Let's create a Future
class containing the result of the calling computation asynchronously with the run
method of the Timer
class, as follows:
Future calc = new Future(computation); calc.then((res) => print(res));
This Future
class does not complete immediately. The Timer
class adds the event to the event queue and executes the computation callback when the event is being processed in the event loop. If the result of the computation
function throws an error, the returned Future
is completed with an error. If the computation
function creates another Future
, the current one will wait until the new Future
is completed and will then be completed with the same result.
In the following code, the Future
class is a scheduled task in the microtasks queue, which does not get completed immediately:
Future calc = new Future.microtask(computation); calc.then((res) => print(res));
If the result of computation
throws, the returned Future
is completed with the error. If computation
creates another Future
, the current one will wait until the new Future
is completed and will then be completed with the same result.
It may sound paradoxical, but we can create a sync version of the Future
class, as follows:
Future calc = new Future.sync(computation); calc.then((res) => print(res));
The reason for this is that the Future
immediately calls the computation
function. The result of the computation will be returned in the next event-loop iteration.
The Future
class can be created with a specified value, as follows:
Future valueFuture = new Future.value(true); valueFuture.then((res) => print(res));
Here, a Future
returns specified value in the next event-loop iteration.
The Future
class can be created with an error, as follows:
try { throw new Error(); } on Error catch(ex, stackTrace) { Future errorFuture = new Future.error(ex, stackTrace); errorFuture.catchError((err, stack) => print(err)); }
This Future
completes with an error in the next event-loop iteration.
Sometimes, it may be necessary to complete Future
after a delay. It can be done as follows:
Future calc = new Future.delayed( new Duration(seconds:1), computation); calc.then((res) => print(res));
The Future
will be completed after the given duration has passed with the result of the computation
function. It always creates an event in the event queue, and the event gets completed no sooner than the next event-loop iteration if the duration is zero or less than zero.
If Future
doesn't have a successor, any error could be silently dropped. In preventing these cases, Dart usually forwards the error to the global error handler.
Let's now look at the benefits of the Future
class:
Futures
Now you know why Dart uses Future
everywhere in its API. The next stop on our journey is zones.
18.226.185.87