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

Как отменить запрос в Dio?

2.0 Middle🔥 161 комментариев
#Асинхронность#Работа с сетью

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

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

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

Отмена запросов в Dio

Dio предоставляет механизм для отмены HTTP запросов с помощью CancelToken. Это полезно при переходе между экранами, закрытии dialogs или timeout'ах.

Основной способ: CancelToken

Создание и использование CancelToken

import 'package:dio/dio.dart';

class UserRepository {
  final Dio dio = Dio();
  CancelToken? _cancelToken;
  
  Future<List<User>> getUsers() async {
    // Отменяем предыдущий запрос если он еще выполняется
    _cancelToken?.cancel('Previous request cancelled');
    
    // Создаем новый токен
    _cancelToken = CancelToken();
    
    try {
      final response = await dio.get(
        'api.example.com/users',
        cancelToken: _cancelToken,
      );
      
      return List<User>.from(
        response.data.map((x) => User.fromJson(x))
      );
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) {
        print('Request cancelled');
      } else {
        print('Error: ${e.message}');
      }
      rethrow;
    }
  }
  
  void cancelRequest() {
    _cancelToken?.cancel('User cancelled');
  }
}

Пример в StatefulWidget

class UserListPage extends StatefulWidget {
  @override
  State<UserListPage> createState() => _UserListPageState();
}

class _UserListPageState extends State<UserListPage> {
  final UserRepository repository = UserRepository();
  late Future<List<User>> _usersFuture;
  
  @override
  void initState() {
    super.initState();
    _usersFuture = repository.getUsers();
  }
  
  @override
  void dispose() {
    // Отменяем запрос при выходе со страницы
    repository.cancelRequest();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Users'),
        actions: [
          IconButton(
            icon: Icon(Icons.close),
            onPressed: () {
              repository.cancelRequest();
              Navigator.pop(context);
            },
          ),
        ],
      ),
      body: FutureBuilder<List<User>>(
        future: _usersFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            final error = snapshot.error;
            if (error is DioException && error.type == DioExceptionType.cancel) {
              return Center(child: Text('Request cancelled'));
            }
            return Center(child: Text('Error: $error'));
          }
          
          return ListView(
            children: snapshot.data!
                .map((user) => ListTile(title: Text(user.name)))
                .toList(),
          );
        },
      ),
    );
  }
}

Отмена нескольких запросов

Использование одного токена для группы запросов

class ApiClient {
  final Dio dio = Dio();
  late CancelToken _cancelToken;
  
  Future<void> fetchMultiple() async {
    _cancelToken = CancelToken();
    
    try {
      final usersFuture = dio.get(
        'api.example.com/users',
        cancelToken: _cancelToken,
      );
      
      final postsFuture = dio.get(
        'api.example.com/posts',
        cancelToken: _cancelToken,
      );
      
      final commentsFuture = dio.get(
        'api.example.com/comments',
        cancelToken: _cancelToken,
      );
      
      // Ждем все три запроса
      await Future.wait([
        usersFuture,
        postsFuture,
        commentsFuture,
      ]);
      
    } catch (e) {
      print('Error: $e');
    }
  }
  
  void cancelAll() {
    _cancelToken.cancel('All requests cancelled');
  }
}

Отмена с timeout

class UserRepository {
  final Dio dio = Dio();
  
  Future<List<User>> getUsersWithTimeout() async {
    final cancelToken = CancelToken();
    
    // Отменяем запрос через 10 секунд
    Future.delayed(Duration(seconds: 10), () {
      cancelToken.cancel('Request timeout exceeded');
    });
    
    try {
      final response = await dio.get(
        'api.example.com/users',
        cancelToken: cancelToken,
      );
      return List<User>.from(
        response.data.map((x) => User.fromJson(x))
      );
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) {
        throw TimeoutException('Request timeout');
      }
      rethrow;
    }
  }
}

Практический пример: поиск с отменой

class SearchRepository {
  final Dio dio = Dio();
  CancelToken? _searchCancelToken;
  
  Future<List<Product>> searchProducts(String query) async {
    // Отменяем предыдущий поиск
    _searchCancelToken?.cancel('New search started');
    
    _searchCancelToken = CancelToken();
    
    try {
      // Задержка перед запросом (debounce)
      await Future.delayed(Duration(milliseconds: 500));
      
      if (_searchCancelToken!.isCancelled) {
        throw DioException(
          requestOptions: RequestOptions(path: ''),
          type: DioExceptionType.cancel,
        );
      }
      
      final response = await dio.get(
        'api.example.com/search',
        queryParameters: {'q': query},
        cancelToken: _searchCancelToken,
      );
      
      return List<Product>.from(
        response.data.map((x) => Product.fromJson(x))
      );
    } on DioException catch (e) {
      if (e.type == DioExceptionType.cancel) {
        print('Search cancelled');
      } else {
        print('Search error: ${e.message}');
      }
      rethrow;
    }
  }
  
  void cancelSearch() {
    _searchCancelToken?.cancel('User cancelled search');
  }
}

// SearchWidget
class SearchWidget extends StatefulWidget {
  @override
  _SearchWidgetState createState() => _SearchWidgetState();
}

class _SearchWidgetState extends State<SearchWidget> {
  final repository = SearchRepository();
  final controller = TextEditingController();
  
  @override
  void dispose() {
    repository.cancelSearch();
    controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      onChanged: (query) {
        if (query.isEmpty) {
          repository.cancelSearch();
        } else {
          // Новый поиск при изменении текста
          repository.searchProducts(query);
        }
      },
    );
  }
}

Проверка статуса отмены

class ApiClient {
  final Dio dio = Dio();
  CancelToken? _cancelToken;
  
  Future<void> longRunningRequest() async {
    _cancelToken = CancelToken();
    
    try {
      final response = await dio.get(
        'api.example.com/data',
        cancelToken: _cancelToken,
      );
      print('Success: ${response.data}');
    } catch (e) {
      // Проверяем был ли запрос отменен
      if (_cancelToken!.isCancelled) {
        print('Request was cancelled');
      } else {
        print('Request failed: $e');
      }
    }
  }
  
  void cancelRequest() {
    if (_cancelToken != null) {
      print('Cancelling request...');
      _cancelToken!.cancel('User cancelled');
    }
  }
  
  bool isRequestActive() {
    return _cancelToken != null && !_cancelToken!.isCancelled;
  }
}

Использование в BLoC

class SearchBloc extends Bloc<SearchEvent, SearchState> {
  final SearchRepository repository;
  
  SearchBloc(this.repository) : super(SearchInitial()) {
    on<SearchQueryChanged>(_onSearchQueryChanged);
    on<SearchCancelled>(_onSearchCancelled);
  }
  
  Future<void> _onSearchQueryChanged(
    SearchQueryChanged event,
    Emitter<SearchState> emit,
  ) async {
    if (event.query.isEmpty) {
      emit(SearchInitial());
      return;
    }
    
    emit(SearchLoading());
    
    try {
      final results = await repository.searchProducts(event.query);
      emit(SearchSuccess(results));
    } catch (e) {
      if (e is DioException && e.type == DioExceptionType.cancel) {
        emit(SearchCancelled());
      } else {
        emit(SearchError(e.toString()));
      }
    }
  }
  
  Future<void> _onSearchCancelled(
    SearchCancelled event,
    Emitter<SearchState> emit,
  ) async {
    repository.cancelSearch();
    emit(SearchCancelled());
  }
}

Итого

Отмена запросов в Dio:

  • Используй CancelToken для отмены запроса
  • Отменяй предыдущий запрос перед новым
  • Отменяй запросы в dispose() при выходе со страницы
  • Проверяй isCancelled перед обработкой результата
  • Используй timeout для автоматической отмены
  • Группируй несколько запросов с одним токеном

Это предотвращает утечки памяти и ненужные операции после закрытия экрана.

Как отменить запрос в Dio? | PrepBro