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.

- 840
- 1
- 11
- 32

- 1,869
- 2
- 19
- 20
-
10The answers are fine. Just for context: You cannot cancel a *future*. Futures are not operations, they are objects representing the *result* of an operation. There is no built-in way to tell a future to tell the underlying operation to stop. That's why all the solutions here are to use something other than a `Future`. That's also why futures in Dart can be *shared*. If anyone could cancel a future for everybody else, you'd have to be much more careful how you share futures. – lrn Sep 24 '21 at 10:21
13 Answers
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.
-
2Kudos for using standard dart libraries, instead of inventing your own wheel. – Alex Semeniuk Aug 07 '19 at 08:52
-
2That's not a standard dart lib though. That's an external dependency. – Andrey Gordeev Apr 23 '20 at 10:19
-
7good point about async. That's super confusing because there is also a native package called async – gsouf May 02 '20 at 17:37
-
4Note that this doesn't work with delayed futures. Those are `Timer` based and the internal timer reference isn't saved so there is no way to prevent execution of the callback once it has begun. – geg Oct 31 '20 at 20:26
-
14this answer is misleading. these classes don't cancel the future itself, rather creat wrappers you can use to imitate the cancellation behavior – nt4f04und Apr 07 '21 at 11:38
How to cancel Future.delayed
A simple way is to use Timer
instead :)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}

- 30,606
- 13
- 135
- 162
-
3
-
-
-
@AndreyGordeev nevermind I had something else in mind. _timer?.cancel() cancels the function from operating after duration timer is over. Instead I wanted to cancel the duration (jump to 0 seconds) to perform the function quicker – Texv Jan 20 '21 at 10:31
-
9@iDecode You can have the `Timer` complete a `Completer`, and callers can `await` the `Completer`'s `Future`. – jamesdlin Feb 07 '21 at 21:21
-
@jamesdlin Oh my goodness, thank you so much sir for sharing that. You always prove (in one way or the other), you're the Best!!! – iDecode Feb 08 '21 at 08:02
-
Nice way to solve the issue. It's not exactly canceling future but it does what I need for the case – Leo Jun 03 '21 at 12:16
-
Thanks you for your suggestion @jamesdlin! I have implemented it and it works perfectly. You can see it in my answer (`CancelableCompleter`). – mr_mmmmore Sep 23 '21 at 16:23
-
There is a simple solution that is cancelable like a Timer AND awaitable like a Future, with no added dependency. See this answer: https://stackoverflow.com/a/69303616/2083587 – mr_mmmmore Aug 30 '22 at 18:26
-
-
1
-
The question clearly states how to cancel a [Future]. Why are you answering for [Future.delayed] specifically? And not even involving [Future] but [Timer] class instead. Completely mis-directioned. – Hitesh Kumar Saini Dec 30 '22 at 10:09
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:

- 13,815
- 5
- 40
- 51
-
I followed your idea. It worked well. The matter was the following. A DAO layer returned 1000 random numbers, each random number being generated by a remote server via 1000 HTTP requests. The DAO layer returned a response including 2 lists : a list of 1000 Future
and a list of 1000 StreamSubscription linked to the 1000 HTTP requests. I used the futures to show the random numbers in a UI and the StreamSubscriptions to cancel the HTTP requests. Thank you ! – Serge Tahé Jul 15 '13 at 10:38 -
Also, do you really need a 1000 HTTP requests? Any reason you cannot just generate a 1000 random numbers using Math.Random? Or have just a single HTTP request that gets you all the numbers? – Shailen Tuli Jul 15 '13 at 12:32
-
I just wanted to test the cancellation of a list of Future. That's the way I found to do this test. Yes I accept the answer. – Serge Tahé Jul 15 '13 at 16:14
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);
}
}

- 237,138
- 77
- 654
- 440
-
1this is great, but completer only can run once. so you can't call _submit() multiple times. Any solution for handling that thing? – Septian Dika Jul 07 '22 at 11:45
One way I accomplished to 'cancel' a scheduled execution was using a Timer
. In this case I was actually postponing it. :)
Timer _runJustOnceAtTheEnd;
void runMultipleTimes() {
_runJustOnceAtTheEnd?.cancel();
_runJustOnceAtTheEnd = null;
// do your processing
_runJustOnceAtTheEnd = Timer(Duration(seconds: 1), onceAtTheEndOfTheBatch);
}
void onceAtTheEndOfTheBatch() {
print("just once at the end of a batch!");
}
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
// will print 'just once at the end of a batch' one second after last execution
The runMultipleTimes()
method will be called multiple times in sequence, but only after 1 second of a batch the onceAtTheEndOfTheBatch
will be executed.

- 5,372
- 1
- 31
- 57
my 2 cents worth...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}

- 507
- 4
- 7
-
-
1@JonScalet I don't think `CancelableOperation` prevents the future callback from executing, whereas the answer does. Try cancelling a `Future.delayed(...)` and it will still execute --- `CancelableOperation.fromFuture(Future.delayed(Duration(seconds: 1), () => print("Finished 1")), onCancel: () => print("Cancelled"),).cancel()` – geg Oct 31 '20 at 20:17
-
@geg You're right, it doesn't actually cancel the computation/execution of the Future. – Jon Scalet Nov 16 '20 at 07:42
-
Nice... but it's not a Future as the name suggests, so it can't be awaited. Sad, because being able to await the `Future` is one of the main reasons I would use a `Future` over a `Timer`. – mr_mmmmore Sep 21 '21 at 16:34
-
For a cancelable delayed future that can be awaited take a look at my answer (`CancelableCompleter`) – mr_mmmmore Sep 23 '21 at 16:21
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.

- 3,189
- 4
- 26
- 34
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.

- 3,857
- 2
- 25
- 35
-
1It seems setting the future's timeout to zero does not cancel the future. Below are code and outputs that prove this. `Future f = new Future.delayed(new Duration(seconds: 2), () => print('2 secs passed')); f.timeout(const Duration(seconds: 0), onTimeout: () { print ('timed out'); });` prints `timed out 2 secs passed` . Catching the raised timeout with catchError, or providing the onTimeout parameter does not make a difference. The future is always ran. – Gazihan Alankus Feb 14 '15 at 22:20
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.

- 1,168
- 9
- 13
Here's a solution to cancel an awaitable delayed future
This solution is like an awaitable Timer
or a cancelable Future.delayed
: it's cancelable like a Timer
AND awaitable like a Future
.
It's base on a very simple class, CancelableCompleter
, here's a demo:
import 'dart:async';
void main() async {
print('start');
// Create a completer that completes after 2 seconds…
final completer = CancelableCompleter.auto(Duration(seconds: 2));
// … but schedule the cancelation after 1 second
Future.delayed(Duration(seconds: 1), completer.cancel);
// We want to await the result
final result = await completer.future;
print(result ? 'completed' : 'canceled');
print('done');
// OUTPUT:
// start
// canceled
// done
}
Now the code of the class:
class CancelableCompleter {
CancelableCompleter.auto(Duration delay) : _completer = Completer() {
_timer = Timer(delay, _complete);
}
final Completer<bool> _completer;
late final Timer? _timer;
bool _isCompleted = false;
bool _isCanceled = false;
Future<bool> get future => _completer.future;
void cancel() {
if (!_isCompleted && !_isCanceled) {
_timer?.cancel();
_isCanceled = true;
_completer.complete(false);
}
}
void _complete() {
if (!_isCompleted && !_isCanceled) {
_isCompleted = true;
_completer.complete(true);
}
}
}
A running example with a more complete class is available in this DartPad.

- 1,934
- 16
- 9
You can use timeout() method
Create a dummy future:
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 10));
return 'Future completed';
}
Setting a timeout of 3 seconds to stop early from 10sec:
_myFuture().timeout(
const Duration(seconds: 3),
onTimeout: () =>
'The process took too much time to finish. Please try again later',
);
and thats it you cancel your FUTURE.

- 157
- 1
- 8
A little class to unregister callbacks from future. This class will not prevent from execution, but can help when you need to switch to another future with the same type. Unfortunately I didn't test it, but:
class CancelableFuture<T> {
Function(Object) onErrorCallback;
Function(T) onSuccessCallback;
bool _wasCancelled = false;
CancelableFuture(Future<T> future,
{this.onSuccessCallback, this.onErrorCallback}) {
assert(onSuccessCallback != null || onErrorCallback != null);
future.then((value) {
if (!_wasCancelled && onSuccessCallback != null) {
onSuccessCallback(value);
}
}, onError: (e) {
if (!_wasCancelled && onErrorCallback != null) {
onErrorCallback(e);
}
});
}
cancel() {
_wasCancelled = true;
}
}
And here is example of usage. P.S. I use provider in my project:
_fetchPlannedLists() async {
if (_plannedListsResponse?.status != Status.LOADING) {
_plannedListsResponse = ApiResponse.loading();
notifyListeners();
}
_plannedListCancellable?.cancel();
_plannedListCancellable = CancelableFuture<List<PlannedList>>(
_plannedListRepository.fetchPlannedLists(),
onSuccessCallback: (plannedLists) {
_plannedListsResponse = ApiResponse.completed(plannedLists);
notifyListeners();
}, onErrorCallback: (e) {
print('Planned list provider error: $e');
_plannedListsResponse = ApiResponse.error(e);
notifyListeners();
});
}
You could use it in situations, when language changed, and request was made, you don't care about previous response and making another request! In addition, I really was wondered that this feature didn't come from the box.

- 778
- 1
- 10
- 23
there is no way unfortunately, take a look:
import 'dart:async';
import 'package:async/async.dart';
void main(List<String> args) async {
final object = SomeTimer();
await Future.delayed(Duration(seconds: 1));
object.dispose();
print('finish program');
}
class SomeTimer {
SomeTimer() {
init();
}
Future<void> init() async {
completer
.complete(Future.delayed(Duration(seconds: 10), () => someState = 1));
print('before wait');
await completer.operation.valueOrCancellation();
print('after wait');
if (completer.isCanceled) {
print('isCanceled');
return;
}
print('timer');
timer = Timer(Duration(seconds: 5), (() => print('finish timer')));
}
Timer? timer;
int _someState = 0;
set someState(int value) {
print('someState set to $value');
_someState = value;
}
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
void dispose() {
completer.operation.cancel();
timer?.cancel();
}
}
after ten seconds you will see someState set to 1
no matter what

- 688
- 6
- 12