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

Riverpod AsyncValue.when未触发错误构建器问题排查

问题根源

你犯了一个Riverpod AsyncNotifier的基础使用错误:AsyncNotifier的泛型类型被错误地设置成了AsyncValue<VirtualBook>,而正确的类型应该是VirtualBook。

AsyncNotifier的设计逻辑是:你只需要在build或异步方法里返回业务数据的Future(比如Future<VirtualBook>),它会自动帮你把这个Future包装成AsyncValue状态(加载中/成功/失败)。但你现在手动返回了AsyncValue<VirtualBook>,导致Riverpod把这个AsyncValue本身当作了业务数据,最终外层又套了一层AsyncValue——也就是你的bookDetails实际类型是AsyncValue<AsyncValue<VirtualBook>>。

这就解释了为什么服务器返回500错误时,when方法会走到data回调:外层的AsyncValue是成功状态,它的data就是你手动返回的那个AsyncError对象,所以你才会在日志里看到data.hasError为true,但此时外层已经是data状态了。

修正代码

修正后的Riverpod代码

final bookDetailsProvider = AutoDisposeAsyncNotifierProviderFamily<
    BookDetailsNotifier, VirtualBook, int>(
  () => BookDetailsNotifier(),
);

class BookDetailsNotifier
    extends AutoDisposeFamilyAsyncNotifier<VirtualBook, int> {
  @override
  Future<VirtualBook> build(int arg) async {
    return _loadBookDetails(arg);
  }

  Future<VirtualBook> _loadBookDetails(int id) async {
    try {
      final res = await ref.read(virualBooksRepositoryProvider).getBookById(id);
      return res; // 直接返回数据,不用包AsyncValue.data
    } on ApiResponseError catch (e, s) {
      printRed('Error Response');
      printRed(e);
      rethrow; // 直接抛出错误,AsyncNotifier会自动包装成AsyncError
    } catch (e, s) {
      printRed(e);
      printRed(s);
      throw 'Something went wrong'; // 抛出错误即可
    }
  }
}

修正后的UI代码(简化部分逻辑)

class BookDetailsPage extends ConsumerWidget {
  const BookDetailsPage(this.bookId, {super.key});
  final int bookId;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final bookDetails = ref.watch(bookDetailsProvider(bookId));

    return AppScaffold(
      titleIcon: Icons.book,
      title: 'Book Details',
      body: Padding(
        padding: const EdgeInsets.all(d_2),
        child: bookDetails.when(
          error: (error, stackTrace) {
            printYellow('Error');
            return Center(child: Text(error.toString()));
          },
          loading: () {
            printYellow('Loading');
            return const Center(child: CircularProgressIndicator());
          },
          data: (book) { // 这里直接拿到VirtualBook类型的数据
            printYellow('Got Data');
            return ListView.separated(
              itemCount: book.chapters.length,
              separatorBuilder: (context, index) => const SizedBox(height: d_2),
              itemBuilder: (context, index) {
                final chapter = book.chapters[index];
                return ChapterTile(
                  index: index,
                  chapter: chapter,
                );
              },
            );
          },
        ),
      ),
    );
  }
}
关键修正点
  • 把AutoDisposeFamilyAsyncNotifier<AsyncValue<VirtualBook>, int>改成AutoDisposeFamilyAsyncNotifier<VirtualBook, int>
  • build和_loadBookDetails的返回类型从Future<AsyncValue<VirtualBook>>改成Future<VirtualBook>
  • 成功时直接返回业务数据,失败时直接抛出错误,不需要手动创建AsyncValue对象

这样修改后,AsyncNotifier会正确地处理异步状态:加载时返回AsyncLoading,成功时返回AsyncData<VirtualBook>,失败时返回AsyncError,UI里的when方法就能正确触发对应的回调了。

内容的提问来源于stack exchange,提问作者Kedar Karki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:33:17