← Назад к вопросам

Как можно отменить Future?

2.3 Middle🔥 191 комментариев
#Dart#Асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

Future в Dart по умолчанию нельзя отменить напрямую — это принципиальный дизайн. Однако существует несколько практических способов реализовать отмену асинхронной операции.

Основная проблема

Future представляет одноразовую асинхронную операцию. Нет встроенного метода cancel(), потому что Future может быть в разных состояниях:

var future = fetchData();
// Как отменить, если fetchData() уже завершается?
// Как отменить, если Future ждёт результата?
future.cancel(); // Нет такого метода!

Способ 1: Использование CancelToken (рекомендуется)

Это самый популярный и надёжный способ. Обычно реализуется через Dio HTTP клиент:

import 'package:dio/dio.dart';

class ApiService {
  late Dio dio;
  CancelToken? _cancelToken;

  ApiService() {
    dio = Dio();
  }

  Future<List<User>> fetchUsers() async {
    _cancelToken = CancelToken();
    try {
      final response = await dio.get(
        'https://api.example.com/users',
        cancelToken: _cancelToken,
      );
      return (response.data as List)
          .map((e) => User.fromJson(e))
          .toList();
    } on DioException catch (e) {
      if (CancelToken.isCancel(e)) {
        print('Request cancelled');
      }
      rethrow;
    }
  }

  void cancelFetchUsers() {
    _cancelToken?.cancel('User cancelled');
  }
}

Способ 2: Собственная реализация с CancelToken

Для операций, не связанных с HTTP:

class MyCancelToken {
  bool _isCancelled = false;
  VoidCallback? _onCancel;

  bool get isCancelled => _isCancelled;

  void setOnCancel(VoidCallback callback) {
    _onCancel = callback;
  }

  void cancel() {
    _isCancelled = true;
    _onCancel?.call();
  }
}

class DataProcessor {
  Future<List<int>> processLargeData(List<int> data) async {
    final cancelToken = MyCancelToken();
    
    try {
      final result = <int>[];
      for (var item in data) {
        if (cancelToken.isCancelled) {
          throw Exception('Processing cancelled');
        }
        await Future.delayed(Duration(milliseconds: 100));
        result.add(item * 2);
      }
      return result;
    } catch (e) {
      print('Error: $e');
      rethrow;
    }
  }
}

Способ 3: Использование StreamController

Для более гибкого управления:

class CancellableFutureService {
  late StreamController<bool> _cancelController;

  CancellableFutureService() {
    _cancelController = StreamController<bool>();
  }

  Future<String> downloadFile(String url) async {
    try {
      final subscription = _cancelController.stream.listen((cancelled) {
        if (cancelled) {
          throw Exception('Download cancelled');
        }
      });

      final result = await _performDownload(url);
      subscription.cancel();
      return result;
    } catch (e) {
      print('Download failed: $e');
      rethrow;
    }
  }

  Future<String> _performDownload(String url) async {
    await Future.delayed(Duration(seconds: 5));
    return 'File downloaded';
  }

  void cancelDownload() {
    _cancelController.add(true);
  }

  void dispose() {
    _cancelController.close();
  }
}

Способ 4: Использование timeout для отмены по времени

Future<String> fetchWithTimeout() async {
  try {
    final result = await fetchData().timeout(
      Duration(seconds: 5),
      onTimeout: () => throw TimeoutException('Request timed out'),
    );
    return result;
  } on TimeoutException {
    print('Request was cancelled due to timeout');
    rethrow;
  }
}

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 10));
  return 'Data';
}

Способ 5: Использование Isolates

Для cpu-intensive операций:

import 'dart:isolate';

class IsolateService {
  Isolate? _isolate;
  SendPort? _sendPort;

  Future<void> startLongOperation() async {
    final receivePort = ReceivePort();
    _isolate = await Isolate.spawn(
      _heavyComputation,
      receivePort.sendPort,
    );

    _sendPort = await receivePort.first;
  }

  void cancelOperation() {
    _isolate?.kill(priority: Isolate.immediate);
    _isolate = null;
  }

  static void _heavyComputation(SendPort sendPort) {
    var sum = 0;
    for (int i = 0; i < 1000000000; i++) {
      sum += i;
    }
    sendPort.send(sum);
  }
}

Способ 6: Riverpod с AsyncValue

В современных Flutter приложениях часто используется Riverpod:

import 'package:riverpod/riverpod.dart';

final usersFutureProvider = FutureProvider<List<User>>((ref) async {
  final cancelToken = CancelToken();
  
  ref.onDispose(() {
    cancelToken.cancel();
  });

  return fetchUsers(cancelToken);
});

class UserListPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(usersFutureProvider);

    return usersAsync.when(
      data: (users) => ListView(
        children: users.map((u) => Text(u.name)).toList(),
      ),
      loading: () => CircularProgressIndicator(),
      error: (err, stack) => Text('Error: $err'),
    );
  }
}

Лучшие практики

  • Используйте CancelToken для HTTP запросов (встроено в Dio)
  • Для других операций используйте собственные флаги отмены
  • Всегда очищайте ресурсы в dispose() или onDispose()
  • Применяйте timeout для предотвращения зависания
  • В Riverpod используйте ref.onDispose() для автоматической отмены

Отмена Future — это не встроенный механизм Dart, но существует множество проверенных паттернов для её реализации.