Flutter中并行HTTP请求的通用处理方案:确保仅应用最新请求响应
Flutter中并行HTTP请求的通用处理方案:确保仅应用最新请求响应
嘿,这个场景我太熟了!快速连续点两次按钮触发请求,结果旧的请求慢悠悠跑完反而覆盖了新请求的结果,loading状态也乱跳,用户体验直接拉胯。别担心,咱们可以搞个通用的封装方案,不用每个请求都写一遍重复逻辑,一劳永逸解决这类问题。
核心思路
本质上我们需要只认最新的那一次请求的响应,要么让旧请求的响应直接失效,要么干脆取消旧请求节省资源。下面给你两种通用的实现方式,随便挑一种都能解决问题。
方案一:请求ID追踪法(无额外依赖)
这种方式不需要加任何第三方包,靠一个计数器来标记请求的“新鲜度”,只有最新请求的响应才会更新UI。
首先在你的State或者ViewModel类里,维护一个计数器变量:
int _latestRequestId = 0; // 假设你的数据列表和loading状态是这些变量 List<dynamic> list = []; bool loading = false;
然后写一个通用的包装方法,所有需要“只认最新请求”的接口都通过它来调用:
Future<void> _runLatestRequest(Future<List<dynamic>> Function() requestFn) async { // 每次发起请求,计数器自增,标记当前请求的ID final currentRequestId = ++_latestRequestId; // 先把loading设为true loading = true; try { // 执行实际的请求 final result = await requestFn(); // 只有当前请求是最新的(ID和最新计数器一致),才更新数据 if (currentRequestId == _latestRequestId) { list.clear(); list.addAll(result); } } catch (e) { // 同样,只有最新请求的错误才需要处理(比如显示错误提示) if (currentRequestId == _latestRequestId) { print('请求出错:$e'); // 这里可以加错误提示逻辑,比如SnackBar之类的 } } finally { // 确保只有最新请求完成后,才把loading设为false if (currentRequestId == _latestRequestId) { loading = false; } } }
原来的defaultData()方法就可以简化成调用这个通用方法:
Future<void> defaultData() => _runLatestRequest( // 把实际的请求逻辑传进去就行 () => apiClient.getData(Endpoints.cityList), );
以后你要加其他类似的请求,比如用户列表请求,直接复用这个包装方法:
Future<void> fetchUserList() => _runLatestRequest( () => apiClient.getData(Endpoints.userList), );
方案二:请求取消法(更高效)
如果想更极致一点,直接把旧请求取消掉,不让它继续占用网络资源,推荐用Dart官方的async包提供的CancelableOperation。
首先在pubspec.yaml里加依赖:
dependencies: async: ^2.11.0 # 版本号可以用最新的
然后在你的类里维护一个当前请求的引用:
CancelableOperation? _currentPendingOperation; List<dynamic> list = []; bool loading = false;
同样写一个通用的包装方法:
Future<void> _runLatestRequest(Future<List<dynamic>> Function() requestFn) async { // 先取消正在进行的旧请求 _currentPendingOperation?.cancel(); loading = true; try { // 把请求包装成可取消的操作 _currentPendingOperation = CancelableOperation.fromFuture(requestFn()); // 等待请求结果 final result = await _currentPendingOperation?.value; // 如果请求没被取消,才更新数据 if (result != null) { list.clear(); list.addAll(result); } } catch (e) { // 忽略取消操作本身的异常 if (e is! CancelException) { print('请求出错:$e'); // 处理实际的请求错误 } } finally { // 只有当前请求没被取消,才更新loading状态 if (_currentPendingOperation?.isCanceled != true) { loading = false; } // 清空当前请求引用 _currentPendingOperation = null; } }
调用方式和方案一完全一样,原来的defaultData()直接复用:
Future<void> defaultData() => _runLatestRequest( () => apiClient.getData(Endpoints.cityList), );
另外,记得在页面销毁的时候取消pending的请求,避免内存泄漏:
@override void dispose() { _currentPendingOperation?.cancel(); super.dispose(); }
两种方案对比
- 方案一:无依赖,实现简单,适合大多数场景,旧请求还是会跑完但不更新UI;
- 方案二:需要依赖官方包,更高效,直接取消旧请求,节省网络和资源,适合频繁触发的请求场景。
不管选哪种,都能帮你实现“只应用最新请求响应”的需求,而且是通用封装,不用每个请求都写重复的判断逻辑~
内容来源于stack exchange
相关产品推荐
相关产品推荐

