Есть ли способ отменить дротик Future?
В пользовательском интерфейсе Dart у меня есть кнопка [submit] для запуска длинного асинхронного запроса. Обработчик [submit] возвращает Future. Далее кнопка [отправить] заменяется кнопкой [отменить], чтобы разрешить отмену всей операции. В обработчике [отмена] я хотел бы отменить длительную операцию. Как я могу отменить будущее, возвращенное обработчиком отправки? Я не нашел никакого способа сделать это.
13 ответов
Насколько я знаю, нет способа отменить будущее. Но есть способ отменить подписку на Stream, и, возможно, это поможет вам.
призвание onSubmit
на кнопку возвращает StreamSubscription
объект. Вы можете явно сохранить этот объект, а затем вызвать cancel()
на нем для отмены потоковой подписки:
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
Позже, в ответ на какое-либо действие пользователя, возможно, вы можете отменить подписку:
Вы можете использовать CancelableOperation или CancelableCompleter, чтобы отменить будущее.
С помощью CancelableOperation
(включен в тест, так что вы можете попробовать сами):
test("CancelableOperation with future", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel();
cancellableOperation.value.then((value) => {
debugPrint('then: $value'),
});
cancellableOperation.value.whenComplete(() => {
debugPrint('onDone'),
});
});
test("CancelableOperation with stream", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel();
cancellableOperation.asStream().listen(
(value) => { debugPrint('value: $value') },
onDone: () => { debugPrint('onDone') },
);
});
Оба вышеуказанных теста будут выводить:
then: future result
whenComplete
Теперь, если мы раскомментируем cancellableOperation.cancel();
тогда оба вышеуказанных теста будут выводить:
onCancel
С помощью CancelableCompleter
если вам нужно больше контроля:
test("CancelableCompleter is cancelled", () async {
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
// completer.operation.cancel();
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'),
});
});
Выход:
isCanceled: false
isCompleted: true
then: future result
onDone
Теперь, если мы раскомментируем cancellableOperation.cancel();
мы получаем вывод:
onCancel
isCanceled: true
isCompleted: true
Помните, что если вы используете await cancellableOperation.value
или же await completer.operation
тогда он никогда не вернет результат и будет ждать бесконечно, если операция была отменена. Это потому что await cancellableOperation.value
так же, как писать cancellableOperation.value.then(...)
но then()
никогда не вызывается, если операция была отменена.
Не забудьте добавить асинхронный пакет Dart.
Как отменить Future.delayed
Простой способ - использовать Timer
вместо этого:)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
@override
void dispose() {
super.dispose();
_timer?.cancel();
}
Одним из способов, которым я смог "отменить" запланированное выполнение, было использование Timer
, В этом случае я фактически откладывал это.:)
Timer _runJustOnceAtTheEnd;
void runMultipleTimes() {
if (_runJustOnceAtTheEnd != null) {
_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
runMultipleTimes()
метод будет вызываться несколько раз подряд, но только через 1 секунду после onceAtTheEndOfTheBatch
будет выполнен.
Для тех, кто пытается достичь этого во Флаттере, вот простой пример того же.
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);
}
}
Мои 2 цента стоят...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}
Существует CancelableOperation в пакете асинхронного на pub.dev, которые вы можете использовать, чтобы сделать это сейчас. Этот пакет не следует путать со встроенной базовой библиотекой dart.
dart:async
, у которого нет этого класса.
Следующий код помогает спроектировать будущую функцию, которая истекает по таймауту и может быть отменена вручную.
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();
}
Тип возврата можно изменить с bool
к вашему собственному типу данных. Completer
объект также следует изменить соответственно.
Измените задачу на будущее с "сделать что-то" на "сделать что-то, если оно не было отменено". Очевидным способом реализации этого было бы установить логический флаг и проверить его в будущем закрытии перед началом обработки, и, возможно, в нескольких точках во время обработки.
Кроме того, это похоже на хак, но установка времени ожидания в будущем на ноль, по-видимому, эффективно отменит будущее.
Вы можете использовать метод timeout()
Создайте фиктивное будущее:
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 10));
return 'Future completed';
}
Установка тайм-аута в 3 секунды для ранней остановки с 10 секунд:
_myFuture().timeout(
const Duration(seconds: 3),
onTimeout: () =>
'The process took too much time to finish. Please try again later',
);
и вот вы отменяете свое БУДУЩЕЕ.
Вот решение, чтобы отменить ожидаемое отложенное будущее
Это решение , как awaitable или отменяемый
Future.delayed
: это отменяется как
Timer
И ждут, как
Future
.
Он основан на очень простом классе,
CancelableCompleter
, вот демонстрация:
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
}
Теперь код класса:
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);
}
}
}
пример с более полным классом доступен в Действующийэтом DartPad .
Небольшой класс для отмены регистрации обратных вызовов из будущего. Этот класс не предотвратит выполнение, но может помочь, когда вам нужно переключиться на другое будущее с тем же типом. К сожалению, не тестировал, но:
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;
}
}
А вот и пример использования. PS Я использую в своем проекте провайдера:
_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();
});
}
Вы можете использовать его в ситуациях, когда язык изменился и был сделан запрос, вам не важен предыдущий ответ и отправка другого запроса! Кроме того, меня действительно удивило, что эта функция не была «из коробки».
нет возможности к сожалению, посмотрите:
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();
}
}
через десять секунд вы увидитеsomeState set to 1
не важно что