← Назад к вопросам
Как отменить запрос в 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 для автоматической отмены
- Группируй несколько запросов с одним токеном
Это предотвращает утечки памяти и ненужные операции после закрытия экрана.