Asynchronous calls and Futures

How should our app handle a situation where it needs a service that can take some time to return its result, for example, when we have to fetch or store data, read a large file, or need a response from a web service. Obviously, the app can't wait for this to end and the result to arrive (the so called synchronous way), because this would freeze the screen (and the app) and frustrate users. A responsive web app must be able to call this service and, immediately, continue with what it was doing. Only when the result returns should it react and call some function on the response data. This is called working in an asynchronous, non-blocking way, and most of the methods in dart:io for server-side Dart apps work in this way. Developers with a Java or C# background would immediately think of starting another thread to do the additional work, but Dart can't do this; it has to compile to JavaScript, so Dart also works in a single-threaded model that is tightly controlled by the browser's event loop.

On the Web (client as well as server), the code has to execute as asynchronously as possible in order not to block the browser from serving its user or a server process from serving its many thousands of client requests. The JavaScript world has long solved this issue using callbacks; this is a function that is "called" when the result of the first function call returns ("backs"). In the following code snippet, the first function that returns the result is doStuff and handle is registered as a callback that works on the result; when an error occurs (onError), handleError is invoked:

doStuff((result) {
  handle(result);
}, onError: (e) {
  handleError(e);
});	

The same mechanism can be used in Dart, but here we have a more elegant way to handle this with objects appropriately called Futures. Now, we define doStuff to return a Future; this is a value (which could be an error) that is not yet available when doStuff is called, but that will be available sometime in the future, after doStuff has asynchronously executed (indicated using the keyword then). The same code snippet written using Futures is much more readable:

doStuff()  
  .then( (result) => handle(result) )
  .catchError( (e) => handleError(e) );

The doStuff method returns a Future object, so it could have been written as:

Future fut1 = doStuff();  
fut1.then( (result) => handle(result) )
    .catchError( (e) => handleError(e) );

But the first or even the following shorter way is idiomatically used:

doStuff()
  .then(handle)
  .catchError(handleError);

Then, it registers the callback handle and catchError calls handleError when an error occurs and stops the error from propagating. It could be considered the asynchronous version of a try/catch construct (there is also a .whenComplete handler that is always executed and corresponds with finally). The advantage becomes even more clear when callbacks are nested to enforce the execution order, because, in the JavaScript way, this results in ugly and difficult-to-read code (sometimes referred to as callback hell). Suppose a computation doStuff2 has to occur between doStuff and handle, the first snippet becomes much less readable:

doStuff((result){doStuff2((result){handle((result) {});
   }, onError: (e) {
  handleError(e);
  });
}, onError: (e) {
  handleError(e);
  });

But, the version using Futures remains very simple:

doStuff()
  .then(doStuff2)
  .then(handle)
  .catchError(handleError);

Through this chaining syntax, it looks like synchronous code, but it is purely asynchronous code executing; catchError catches any errors that occur in the chain of Futures. As a simple, but working, example, suppose an app future1 needs to show or process a large file bigfile.txt and we don't want to wait until this I/O is completely done:

import 'dart:io';                                            (1)
import 'dart:async';                                         (2)

main() {
  var file = new File('bigfile.txt'),                        (3)
  file.readAsString()                                        (4)
    .then( (text) => print(text) )                           (5)
    .catchError( (e) => print(e) );                          (6)
  // do other things while file is read in
  ...                                                        (7)
}

To work with files and directories, dart:io is needed in line (1); the Future functionality comes from dart:async (line (2)). In line (3), a File object is created, and, in line (4), the action to read the file in is started asynchronously; but, the program immediately continues executing lines (7) and beyond. When the file is completely read through, line (5) prints its contents; should an error e (for example, a non-existing file) have occurred, this is printed in line (6). You can even leave out the intermediary variables and write:

  file.readAsString()
    .then(print)
 .catchError(print);

Note

You can find more info in the following article:

https://www.dartlang.org/articles/futures-and-error-handling/

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

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