is there any way to cancel a dart Future?

Serge Tahé picture Serge Tahé · Jul 9, 2013 · Viewed 23k times · Source

In a Dart UI, I have a button [submit] to launch a long async request. The [submit] handler returns a Future. Next, the button [submit] is replaced by a button [cancel] to allow the cancellation of the whole operation. In the [cancel] handler, I would like to cancel the long operation. How can I cancel the Future returned by the submit handler? I found no method to do that.

Answer

vovahost picture vovahost · Feb 27, 2019

You can use CancelableOperation or CancelableCompleter to cancel a future. See below the 2 versions:

Solution 1: CancelableOperation (included in a test so you can try it yourself):

  • cancel a future

test("CancelableOperation with future", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

// cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.value.then((value) => {
    debugPrint('then: $value'),
  });
  cancellableOperation.value.whenComplete(() => {
    debugPrint('onDone'),
  });
});
  • cancel a stream

test("CancelableOperation with stream", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

  //  cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.asStream().listen(
        (value) => { debugPrint('value: $value') },
    onDone: () => { debugPrint('onDone') },
  );
});

Both above tests will output:

then: future result
onDone

Now if we uncomment the cancellableOperation.cancel(); then both above tests will output:

onCancel

Solution 2: CancelableCompleter (if you need more control)

test("CancelableCompleter is cancelled", () async {

  CancelableCompleter completer = CancelableCompleter(onCancel: () {
    print('onCancel');
  });

  // completer.operation.cancel();  // uncomment this to test cancellation

  completer.complete(Future.value('future result'));
  print('isCanceled: ${completer.isCanceled}');
  print('isCompleted: ${completer.isCompleted}');
  completer.operation.value.then((value) => {
    print('then: $value'),
  });
  completer.operation.value.whenComplete(() => {
    print('onDone'),
  });
});

Output:

isCanceled: false
isCompleted: true
then: future result
onDone

Now if we uncomment the cancellableOperation.cancel(); we get output:

onCancel
isCanceled: true
isCompleted: true

Be aware that if you use await cancellableOperation.value or await completer.operation then the future will never return a result and it will await indefinitely if the operation was cancelled. This is because await cancellableOperation.value is the same as writing cancellableOperation.value.then(...) but then() will never be called if the operation was cancelled.

Remember to add async Dart package.

Code gist