You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:23:02