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

Flutter Dio拦截器报错:Bad state: Future already completed 求助解决

解决Dio拦截器JWT刷新时的"Future already completed"错误

我实现了一个Dio拦截器,用于发送jwt token,并在JWT过期时调用refresh_token接口。当JWT过期时,遇到了如下错误:

Error: Bad state: Future already completed

请求仍能正常处理,但控制台中会先出现一个成功响应,随后出现一个401错误。


custom_interceptor.dart

class CustomInterceptor extends DefaultInterceptor {
  ISecureStorage secureStorageService = ISecureStorage();

  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    LoginModel loginModel = await secureStorageService.readLoginModel();

    options.headers = {
      "Content-type": "application/json",
      "Authorization": "Bearer ${loginModel.access_token}"
    };
    return super.onRequest(options, handler);
  }

  @override
  void onError(err, handler) async {
    if (err.response?.statusCode == 401) {
      final Dio _dio = DioConfig().dio;
      LoginModel loginModel = await secureStorageService.readLoginModel();
      Uri uri = Uri.https(
          "$BASE_URL", "/refresh_token_url");
      try {
        await _dio.postUri(uri, data: {
          "refresh_token": loginModel.refresh_token,
          "grant_type": "refresh_token"
        }).then((value) async {
          if (value?.statusCode == 200) {

            await secureStorageService.deleteLoginModel();
            LoginModel newLoginData = LoginModel.fromJson(value.data);
            await secureStorageService.saveLoginModel(loginModel: newLoginData);
            
            err.requestOptions.headers["Authorization"] =
                "Bearer " + newLoginData.refresh_token;

            final opts = new Options(
                method: err.requestOptions.method,
                headers: err.requestOptions.headers);
            final cloneReq = await _dio.request(err.requestOptions.path,
                options: opts,
                data: err.requestOptions.data,
                queryParameters: err.requestOptions.queryParameters);
            return handler.resolve(cloneReq);
          }
          return err;
        });
        return super.onError(err, handler);
      } catch (e, st) {
        print("ERROR: " + e);
        print("STACK: " + st.toString());
        return super.onError(err, handler);
      }
    } else {
      return super.onError(err, handler);
    }
  }
}
class DefaultInterceptor extends Interceptor {
  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    print(
        'REQUEST[${options.method}] => PATH: ${options.path} | DATA => ${options.data} | JWT => ${options.headers}');
    return super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print(
        'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path} | DATA => ${response.data}');
    super.onResponse(response, handler);
    return;
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    print(
        'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path} | SENT_DATA => ${err.requestOptions.data} | RECEIVED_DATA => ${err.response?.data}');
    return super.onError(err, handler);
  }
}

dio_config.dart

class DioConfig {
    static DioConfig _singletonHttp;
    Dio _dio;

    get dio => _dio;

    factory DioConfig() {
        _singletonHttp ??= DioConfig._singleton();
        return _singletonHttp;
    }

    DioConfig._singleton() {
        _dio = Dio();
    }

    dispose() {
        _dio.close();
    }
}

i_secure_storage.dart

abstract class ISecureStorage {

    factory ISecureStorage() => getSecureStorage();

    Future<LoginModel> readLoginModel() async => LoginModel.empty;

    Future<bool> saveLoginModel({LoginModel loginModel}) async => false;

    Future<bool> deleteLoginModel() async => false;
}

web_secure_storage.dart

ISecureStorage getSecureStorage() => WebSecureStorageService();

class WebSecureStorageService implements ISecureStorage {

    final String _loginData = 'loginData';
    html.Storage webStorage = html.window.localStorage;

    @override
    Future<LoginModel> readLoginModel() async {
        return webStorage[_loginData] == null
                ? LoginModel.empty
                : LoginModel.fromJson(jsonDecode(webStorage[_loginData]));
    }

    @override
    Future<bool> saveLoginModel({ LoginModel loginModel}) async {
        webStorage[_loginData] = jsonEncode(loginModel);
        return true;
    }

    @override
    Future<bool> deleteLoginModel() async {
        webStorage.remove(_loginData);
        return true;
    }
}

mobile_secure_storage.dart

ISecureStorage getSecureStorage() => MobileSecureStorageService();

class MobileSecureStorageService implements ISecureStorage {
    final String _loginModel = 'loginModel';

    FlutterSecureStorage storage = const FlutterSecureStorage();

    @override
    Future<LoginModel> readLoginModel() async {
        try {
            dynamic _loginData = await storage.read(key: _loginModel);
            return _loginData == null ? LoginModel.empty : LoginModel.fromJson(jsonDecode(_loginData));
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }

    @override
    Future<bool> saveLoginModel({LoginModel loginModel}) async {
        try {
            await storage.write(key: _loginModel, value: jsonEncode(loginModel));
            return true;
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }

    @override
    Future<bool> deleteLoginModel() async {
        try {
            await storage.delete(key: _loginModel);
            return true;
        } on PlatformException catch (ex) {
            throw PlatformException(code: ex.code, message: ex.message);
        }
    }
}

解决方案

问题出在onError方法中,当成功刷新token并重新发起请求后,代码仍然执行了return super.onError(err, handler);,导致handler被多次调用,触发了"Future already completed"错误。

将以下代码:

return super.onError(err, handler);

修改为:

return null;

修改后,当成功处理刷新逻辑并调用handler.resolve(cloneReq)后,不会再执行父类的错误处理逻辑,避免了重复完成Future的问题,控制台的错误和401响应也会消失。

内容的提问来源于stack exchange,提问作者Vi.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 21:54:15