Future

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:

  • The incomplete state is an initial state when Future is waiting for the result. The result field holds a single-linked list of Future listeners.
  • A completed state with a value as the result.
  • A completed state with an error as the result.
  • A 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.

    Note

    Future can be completed with a value or an error only once.

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.

Note

The catchError method is the asynchronous equivalent of a catch block.

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).

Note

The whenComplete method is the asynchronous equivalent of a finally block.

Now when we are finished with definitions, let's discuss the different factory constructors of Future.

Future and Timer

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.

Future and Microtask

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.

Sync the Future class

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.

Future with a value

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.

Future with an error

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.

Delaying the Future class

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.

Note

The Future class must not change the completed value or the error to avoid side effects from listeners.

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:

  • It has a consistent pattern to handle callbacks and exceptions
  • It is a more convenient way when compared to chain operations
  • It is easy to combine Futures
  • It provides a single control flow to develop web and command-line applications

Now you know why Dart uses Future everywhere in its API. The next stop on our journey is zones.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.185.87