Error handling with Futures

This recipe shows you how to handle errors comprehensively when working with Futures. The accompanying code future_errors.dart (inside the bin map in the future_errors project) illustrates the different possibilities; however, this is not a real project, so it is not meant to be run as is.

Getting ready

When the function that returns a Future value completes successfully (calls back) signaled in the code by then, a callback function handleValue is executed that receives the value returned. If an error condition took place, the callback handleError handles it. Let's say this function is getFuture(), with Future as the result and a return value of type T, then this becomes equivalent to the following code:

Future<T> future = getFuture();
future.then(handleValue)
.catchError(handleError);

handleValue(val) {
  // processing the value
}

handleError(err) {
  // handling the error
}

The highlighted code is sometimes also written as follows, only to make the return values explicit:

future.then( (val) =>handleValue(val) )
.catchError( (err) =>handleError(err) );

When there is no return value, this can also be written as shown in the following code:

future.then( () =>nextStep() )

When the return value doesn't matter in the code, this can be written with an _ in place of that value, as shown in the following code:

future.then( (_) =>nextStep(_) )

But, in any case, we prefer to write succinct code, as follows:

future.then(nextStep)

The then and catcherror objects are chained as they are called, but that doesn't mean that they are both executed. Only one executes completely; compare it to the try-catch block in synchronous code. The catcherror object can even catch an error thrown in handleValue.

This is quite an elegant mechanism, but what do we do when the code gets a little more complicated?

How to do it...

Let's see the different ways you can work with Futures in action:

  • Chaining Futures: Let's suppose we have a number of steps that each will return a Future and so run asynchronously, but the steps have to execute in a certain order. We could chain these as shown in the following code:
    firstStep()
        .then((_) =>secondStep())
        .then((_) =>thirdStep())
        .then((_) =>fourthStep())
        .catchError(handleError);
    
  • Concurrent Futures: If, on the other hand, all the steps return a value of the type T, and their order of execution is not important, you can use the static method of Future, wait, as shown in the following code:
    List futs = [firstStep(), secondStep(), thirdStep(), fourthStep()];
    Future.wait(futs)
    .then((_) =>processValues(_))
    .catchError(handleError);
  • Run the script wait_error.dart to see what happens when an error occurs in one of the steps (either by throw or a Future.error call):
    import'dart:async';
    
    main() {
      Future<int> a = new Future(() {
      print('a'),
      return 1;
    });
    Future<int> b = new Future.error('Error occured in b!'),
    Future<int> c = new Future(() {
      print('c'),
      return 3;
    });
    Future<int> d = new Future(() {
      print('d'),
      return 4;
    });
    
    Future.wait([a, b, c, d]).then((List<int> values) => print(values)).catchError(print);
      print('happy end'),
    }

    The output is as follows:

    happy end

    a

    c

    d

    Error occurred in b!

  • The following code helps to catch a specific error or more than one error:
    firstStep()
    .then((_) =>secondStep())
        // more .then( steps )
    .catchError(handleArgumentError,
    test: (e) => e is ArgumentError)
    .catchError(handleFormatException,
    test: (e) => e is FormatException)
    .catchError(handleRangeError,
    test: (e) => e is RangeError)
    .catchError(handleException, test: (e) => e is Exception);
    
  • Often, you want to execute a method that does a cleanup after asynchronous processing no matter whether this processing succeeds or ends in an error. In that case, use whenComplete:
    firstStep()
    .then((_) =>secondStep())
    .catchError(handleError)
    .whenComplete(cleanup);
    

With respect to handling synchronous and asynchronous errors, let's suppose that we want to call a function mixedFunction, with a synchronous call to synFunc that could throw an exception and an asynchronous call to asynFunc that could do likewise, as shown in the following code:

mixedFunction(data) {
  var var2 = new Var2();
  var var1 = synFunc(data);         // Could throw error.
  return var2.asynFunc().then(processResult);  // Could throw error.
}

If we call this function mixedFunction(data).catchError(handleError);, then catchError cannot catch an error thrown by synFunc. To solve this, they call in a Future.sync, function as shown in the following code:

mixedFunction(data) {
  return new Future.sync(() {
  var var1 = synFunc(data);         // Could throw error.
  return var1.asynFunc().then(processResult);  // Could throw error.
  });
}

That way, catchError can catch both synchronous and asynchronous errors.

How it works...

In variation 1, catchError will handle all errors that occur in any of the executed steps. For variation 2, we make a list with all the steps. The Future.wait option will do exactly as its name says: it will wait until all of the steps are completed. But they are executed in no particular order, so they can run concurrently. All of the functions are triggered without first waiting for any particular function to complete. When they are all done, their return values are collected in a list (here called val) and can be processed. Again, catchError handles any possible error that occurs in any of the steps.

In the case of an error, the List value is not returned; we see that, in the example on wait_error, happy end is first printed, then a, c, and d complete, and then the error from b is caught; if d also throws an error, only the b error is caught. The catchError function doesn't know in which step the error occurred unless that is explicitly conveyed in the error.

In the same way as in the catch block, we can also test in catchError when a specific exception occurs using its second optional test argument, where you test the type of the exception. This is shown in variation 3; be sure then, to test for a general exception as the last clause. This scenario will certainly be useful if a number of different exceptions can occur and we want a specific treatment for each of them.

Analogous to the optional finally clause in a try statement, asynchronous processing can have a whenComplete handler as in variation 4, which always executes whether there is an error or not. Use it to clean up and close files, databases, and network connections, and so on.

Finally, in variation 5, the normal catchError function won't work, because it can only handle exceptions arising from asynchronous code execution. Use Future.synchere, which is able to return the result or error from both synchronous and asynchronous method calls.

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

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