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);
You can find more info in the following article:
https://www.dartlang.org/articles/futures-and-error-handling/
18.216.117.191