is there any way to cancel a dart Future?

后端 未结 9 1449
礼貌的吻别
礼貌的吻别 2020-11-27 05:55

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 a

相关标签:
9条回答
  • 2020-11-27 05:58

    Change the future's task from 'do something' to 'do something unless it has been cancelled'. An obvious way to implement this would be to set a boolean flag and check it in the future's closure before embarking on processing, and perhaps at several points during the processing.

    Also, this seems to be a bit of a hack, but setting the future's timeout to zero would appear to effectively cancel the future.

    0 讨论(0)
  • 2020-11-27 05:58

    The following code helps to design the future function that timeouts and can be canceled manually.

    import 'dart:async';
    
    class API {
      Completer<bool> _completer;
      Timer _timer;
    
      // This function returns 'true' only if timeout >= 5 and
      // when cancelOperation() function is not called after this function call.
      //
      // Returns false otherwise
      Future<bool> apiFunctionWithTimeout() async {
        _completer = Completer<bool>();
        // timeout > time taken to complete _timeConsumingOperation() (5 seconds)
        const timeout = 6;
    
        // timeout < time taken to complete _timeConsumingOperation() (5 seconds)
        // const timeout = 4;
    
        _timeConsumingOperation().then((response) {
          if (_completer.isCompleted == false) {
            _timer?.cancel();
            _completer.complete(response);
          }
        });
    
        _timer = Timer(Duration(seconds: timeout), () {
          if (_completer.isCompleted == false) {
            _completer.complete(false);
          }
        });
    
        return _completer.future;
      }
    
      void cancelOperation() {
        _timer?.cancel();
        if (_completer.isCompleted == false) {
          _completer.complete(false);
        }
      }
    
      // this can be an HTTP call.
      Future<bool> _timeConsumingOperation() async {
        return await Future.delayed(Duration(seconds: 5), () => true);
      }
    }
    
    void main() async {
      API api = API();
      api.apiFunctionWithTimeout().then((response) {
        // prints 'true' if the function is not timed out or canceled, otherwise it prints false
        print(response);
      });
      // manual cancellation. Uncomment the below line to cancel the operation.
      //api.cancelOperation();
    }
    

    The return type can be changed from bool to your own data type. Completer object also should be changed accordingly.

    0 讨论(0)
  • 2020-11-27 06:03

    For those, who are trying to achieve this in Flutter, here is the simple example for the same.

    class MyPage extends StatelessWidget {
      final CancelableCompleter<bool> _completer = CancelableCompleter(onCancel: () => false);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("Future")),
          body: Column(
            children: <Widget>[
              RaisedButton(
                child: Text("Submit"),
                onPressed: () async {
                  // it is true only if the future got completed
                  bool _isFutureCompleted = await _submit();
                },
              ),
              RaisedButton(child: Text("Cancel"), onPressed: _cancel),
            ],
          ),
        );
      }
    
      Future<bool> _submit() async {
        _completer.complete(Future.value(_solve()));
        return _completer.operation.value;
      }
    
      // This is just a simple method that will finish the future in 5 seconds
      Future<bool> _solve() async {
        return await Future.delayed(Duration(seconds: 5), () => true);
      }
    
      void _cancel() async {
        var value = await _completer.operation.cancel();
        // if we stopped the future, we get false
        assert(value == false);
      }
    }
    
    0 讨论(0)
  • 2020-11-27 06:10

    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

    0 讨论(0)
  • 2020-11-27 06:13

    As far as I know, there isn't a way to cancel a Future. But there is a way to cancel a Stream subscription, and maybe that can help you.

    Calling onSubmit on a button returns a StreamSubscription object. You can explicitly store that object and then call cancel() on it to cancel the stream subscription:

    StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
    
       // you code here
    
       if (someCondition == true) {
         subscription.cancel();
       }
    });
    

    Later, as a response to some user action, perhaps, you can cancel the subscription:

    0 讨论(0)
  • 2020-11-27 06:14

    There is a CancelableOperation in the async package on pub.dev that you can use to do this now. This package is not to be confused with the built in dart core library dart:async, which doesn't have this class.

    0 讨论(0)
提交回复
热议问题