Nerdy Bunz
Nerdy Bunz

Reputation: 7437

How to make a delayed future cancelable in Dart?

Lets say that in Dart/Flutter you have the following code:

void myOperation() {
  // could be anything
  print('you didn't cancel me!');
}

Notice that the operation itself is not asynchronous and is void -- does not return anything.

We want it to execute at some point in the future, but we also want to be able to cancel it (because a new operation has been requested that supersedes it).

I've started by doing this:

Future.delayed(Duration(seconds: 2), myOperation())

... but this is not cancellable.

How exactly could you schedule that "operation," but also make it cancelable?

I'm thinking... we could modify the code like so:

Future.delayed(Duration(seconds: 2), () {
    if (youStillWantThisToExecute) {
        print('you didn't cancel me!');
    }
});

But that's not really very good because it depends on a "global" boolean... and so if the boolean gets flipped to false, no operations will complete, even the most recently requested, which is the one we want to complete.

It would be nicer if there were a way to create any number of instances of the operation and cancel them on an individual basis... or to have a unique id assigned to each operation, and then instead of having a boolean control whether or not to execute... to have a "mostRecentId" int or something which is checked prior to execution.

Anyways...

CancelableOperation seemed promising just from its name.

So, I looked at its documentation:

CancelableOperation.fromFuture(Future inner, {FutureOr onCancel()}) Creates a CancelableOperation wrapping inner. [...] factory

But honestly that just makes my poor head hurt oh so much.

I've consulted other articles, questions, and answers, but they are all part of some specific (and complex) context and there isn't a dirt simple example anywhere to be found.

Is there a way to make a delayed future cancellable by wrapping it in some other class?

Can someone more experienced please provide at least one simple, complete, verified example that compiles in DartPad?

Thanks.

Upvotes: 5

Views: 4677

Answers (3)

jamesdlin
jamesdlin

Reputation: 89965

You cannot cancel an existing Future. If you do:

Future.delayed(
  Duration(seconds: 2),
  () {
    print('hello');
  },
);

as long as the process runs (and is processing its event queue) for at least 2 seconds, the Future eventually will execute and print 'hello'.

At best you can cause one of the Future's completion callbacks to fire prematurely so that callers can treat the operation as cancelled or failed, which is what CancelableOperation, et al. do.

Edit:

Based on your updated question, which now asks specifically about delayed Futures, you instead should consider using a Timer, which is cancelable. (However, unlike a Future, callers cannot directly wait on a Timer. If that matters to you, you would need to create a Completer, have callers wait on the Completer's Future, and let the Timer's callback complete it.)

Upvotes: 1

Maxim Saplin
Maxim Saplin

Reputation: 4652

Use Timer:

var t = Timer(Duration(seconds: 400), () async {
   client.close(force: true);
});
...
t.cancel();

Upvotes: 4

YoBo
YoBo

Reputation: 2529

Using CancalableOperation will not stop print('hello'); from executing even if you cancel. What it does is canceling(discarding) the result(void in your case). I will give you 2 examples using CancalableOperation and CancalableFuture.

CancelableOperation example

    final delayedFuture = Future.delayed(
      Duration(seconds: 2),
      () {
        return 'hello';
      },
    );

    final cancellableOperation = CancelableOperation.fromFuture(
      delayedFuture,
      onCancel: () => {print('onCancel')},
    );

    cancellableOperation.value.then((value) => {
          // Handle the future completion here
          print('then: $value'),
        });
    cancellableOperation.value.whenComplete(() => {
          print('onDone'),
        });
    cancellableOperation.cancel(); // <- commment this if you want to complete

CancelableFuture example

    final delayedFuture = ...;

    final cancalableFuture = CancelableFuture<String>(
      future: delayedFuture,
      onComplete: (result) {
        // Use the result from the future to do stuff
        print(result);
      },
    );
    cancalableFuture.cancel(); // <- commment this if you want to complete

And the CancelableFuture implementation

class CancelableFuture<T> {
  bool _cancelled = false;
  CancelableFuture({
    @required Future<dynamic> future,
    @required void Function(T) onComplete,
  }) {
    future.then((value) {
      if (!_cancelled) onComplete(value);
    });
  }
  void cancel() {
    _cancelled = true;
  }
}

Upvotes: 2

Related Questions