使用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
相关产品推荐
相关产品推荐

