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

Riverpod 2.0:如何用FutureOr和AutoDisposeAsyncNotifier处理API状态码?

Riverpod中API请求状态与错误的优雅处理

问题场景

你当前的登录Provider代码未处理API非200状态码的情况,当响应体无法解析为Map<String, dynamic>时,jsonDecode会直接抛出未捕获的异常,导致Riverpod无法切换到AsyncError状态,UI层也就无法正常处理错误场景。

原Provider代码:

final LoginAsyncProvider = AsyncNotifierProvider.autoDispose<LoginProvider, User>(() {
  return LoginProvider();
});

class LoginProvider extends AutoDisposeAsyncNotifier<User> {

  FutureOr<User> login({required String userName, required String password}) async {
    final response = await http.post(Uri.https(Api.mainDomain, Api.login));
    final json = jsonDecode(response.body) as Map<String, dynamic>;
    User _user = User.fromJson(json);
    state = AsyncData(_user);
    return _user;
  }

  @override
  FutureOr<User> build() async {
    return login(userName: "someUser", password: "somePass");  }
}

UI状态处理代码:

...
child: switch (userAsyncValue) { 
  AsyncData(:final value) => Text(text: value.userName),
  AsyncError() => const Text('Some Error happened'),
  _ => const CircularProgressIndicator(),
},
...

解决方案

要优雅处理API请求的各类失败场景,需在Provider中主动捕获状态码异常、解析异常,并将错误信息封装后抛出,让Riverpod自动切换为AsyncError状态,最终在UI层统一处理。

1. 自定义API异常类型

先定义异常类区分错误类型,方便UI层展示精准提示:

enum ApiErrorType {
  invalidStatusCode,
  parseError,
  unknown,
}

class ApiException implements Exception {
  final ApiErrorType type;
  final String message;

  ApiException({required this.type, required this.message});
}

2. 改造LoginProvider登录逻辑

在login方法中分层处理状态码校验、数据解析,任何环节出错都抛出自定义异常,并确保Riverpod能捕获到错误状态:

class LoginProvider extends AutoDisposeAsyncNotifier<User> {

  Future<User> login({required String userName, required String password}) async {
    try {
      // 传递登录参数(原代码遗漏此步骤)
      final response = await http.post(
        Uri.https(Api.mainDomain, Api.login),
        body: {'userName': userName, 'password': password},
      );

      // 校验状态码
      if (response.statusCode != 200) {
        String errorMsg;
        switch (response.statusCode) {
          case 401:
            errorMsg = "用户名或密码错误";
            break;
          case 404:
            errorMsg = "登录接口不存在";
            break;
          default:
            errorMsg = "请求失败,状态码:${response.statusCode}";
        }
        throw ApiException(
          type: ApiErrorType.invalidStatusCode,
          message: errorMsg,
        );
      }

      // 解析响应体
      late final Map<String, dynamic> json;
      try {
        json = jsonDecode(response.body) as Map<String, dynamic>;
      } catch (e) {
        throw ApiException(
          type: ApiErrorType.parseError,
          message: "数据解析失败:${e.toString()}",
        );
      }

      // 转换为User对象(若fromJson可能抛错,可额外加try-catch)
      final User _user = User.fromJson(json);
      state = AsyncData(_user);
      return _user;
    } catch (e) {
      // 更新状态为AsyncError并重新抛出,确保Riverpod同步状态
      state = AsyncError(e, StackTrace.current);
      rethrow;
    }
  }

  @override
  FutureOr<User> build() async {
    // 建议返回初始空状态,而非硬编码测试请求
    // 登录操作由UI层主动触发:ref.read(LoginAsyncProvider.notifier).login(...)
    return User.empty();
  }
}

3. UI层优化错误处理

现在可在UI层根据自定义异常类型展示不同提示:

...
child: switch (userAsyncValue) { 
  AsyncData(:final value) => Text(value.userName),
  AsyncError(:final error) when error is ApiException => 
    Text((error as ApiException).message),
  AsyncError() => const Text('未知错误'),
  _ => const CircularProgressIndicator(),
},
...

关键注意事项

  • 必须重新抛出异常:在Provider的catch块中更新AsyncError状态后,一定要rethrow,否则AsyncNotifier无法将错误状态同步给UI。
  • 避免build方法硬编码请求:build方法会在Provider初始化时自动执行,建议返回初始空状态,登录操作由UI层主动触发。
  • 精准区分错误类型:自定义异常能让UI层展示更贴合场景的错误提示,提升用户体验。

内容的提问来源于stack exchange,提问作者J. O'Ryan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:22:37