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

使用Dio拦截器刷新Token时,带FormData的请求重试报错:Bad state: The FormData has already been finalized

使用Dio拦截器刷新Token时,带FormData的请求重试报错:Bad state: The FormData has already been finalized

我之前也碰到过一模一样的问题!其实根源很简单:FormData是基于流的对象,只能被消费一次。第一次请求发送后,Dio会把FormData标记为finalized(已完成处理),当拦截器触发401重试时,直接复用原来的请求选项,里面的FormData已经无法再次读取,就会抛出这个错误。

给你几个实用的解决思路,亲测有效:

方法一:重试时重新构建FormData(最推荐)

核心思路是在发起请求时,把FormData的原始数据存在请求的extra字段里,这样重试时可以拿这些原始数据重新生成一份全新的FormData,而不是复用已被消费的旧对象。

步骤1:发起请求时保存原始数据

比如你上传文件的请求可以这么写:

// 准备FormData的原始数据(注意文件要存路径而不是MultipartFile对象)
var formRawData = {
  'username': 'test',
  'avatarPath': '/storage/emulated/0/avatar.jpg', // 文件本地路径
};
// 生成当前请求用的FormData
var formData = FormData.fromMap({
  'username': formRawData['username'],
  'avatar': await MultipartFile.fromFile(formRawData['avatarPath']),
});

// 发起请求,把原始数据存在extra里
await _dio.post('/user/upload',
  data: formData,
  options: Options(extra: {'originalFormData': formRawData})
);

步骤2:拦截器里重试时重构FormData

修改你的拦截器onError逻辑:

onError: (error, handler) async {
  // 只处理401的情况
  if (error.response?.statusCode == 401) {
    final newAccessToken = await refreshToken();
    if (newAccessToken != null) {
      // 复制原请求的选项,避免修改全局配置
      final retryOptions = error.requestOptions.copyWith();
      // 给重试请求设置新的Token
      retryOptions.headers["Authorization"] = 'Bearer $newAccessToken';

      // 判断是否是FormData请求,重构数据
      if (retryOptions.extra.containsKey('originalFormData')) {
        var rawData = retryOptions.extra['originalFormData'];
        var newFormData = FormData.fromMap({
          'username': rawData['username'],
          'avatar': await MultipartFile.fromFile(rawData['avatarPath']),
        });
        retryOptions.data = newFormData;
      }

      // 发起重试请求,然后把结果返回
      final retryResponse = await _dio.request(
        retryOptions.path,
        options: retryOptions,
        data: retryOptions.data,
      );
      return handler.resolve(retryResponse);
    }
  }
  // 非401错误直接交给下一个处理器
  return handler.next(error);
},

方法二:避免全局修改headers

你原来的代码里直接修改_dio.options.headers,这其实不太安全——如果有多个并发请求,可能会导致其他请求拿到错误的Token。上面的方法里用retryOptions.copyWith()单独设置重试请求的headers,能避免这个问题。

注意事项

  • 如果FormData里有多个文件或者复杂字段,一定要把所有原始数据都存在extra里,不要遗漏任何字段;
  • 确保文件路径在重试时依然有效(比如用户没有删除文件),如果是临时文件可能需要额外处理;
  • 如果你不想手动存原始数据,也可以尝试在拦截器里对FormData进行深拷贝,但这种方式对文件类型的FormData不太友好,不如上面的方法可靠。

备注:内容来源于stack exchange,提问作者Hemmyhtec Creatives

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 03:43:05