guard<T> static method

Future<AsyncValue<T>> guard<T>(
  1. Future<T> future(), [
  2. bool test(
    1. Object
    )?
])

Transforms a Future that may fail into something that is safe to read.

This is useful to avoid having to do a tedious try/catch. Instead of writing:

class MyNotifier extends AsyncNotifier<MyData> {
  @override
  Future<MyData> build() => Future.value(MyData());

  Future<void> sideEffect() async {
    state = const AsyncValue.loading();
    try {
      final response = await dio.get('my_api/data');
      final data = MyData.fromJson(response);
      state = AsyncValue.data(data);
    } catch (err, stack) {
      state = AsyncValue.error(err, stack);
    }
  }
}

We can use guard to simplify it:

class MyNotifier extends AsyncNotifier<MyData> {
  @override
  Future<MyData> build() => Future.value(MyData());

  Future<void> sideEffect() async {
    state = const AsyncValue.loading();
    // does the try/catch for us like previously
    state = await AsyncValue.guard(() async {
      final response = await dio.get('my_api/data');
      return Data.fromJson(response);
    });
  }
}

An optional callback can be specified to catch errors only under a certain condition. In the following example, we catch all exceptions beside FormatExceptions.

  AsyncValue.guard(
   () async { /* ... */ },
    // Catch all errors beside [FormatException]s.
   (err) => err is! FormatException,
  );
}

Implementation

static Future<AsyncValue<T>> guard<T>(
  Future<T> Function() future, [
  bool Function(Object)? test,
]) async {
  try {
    return AsyncValue.data(await future());
  } catch (err, stack) {
    if (test == null) {
      return AsyncValue.error(err, stack);
    }
    if (test(err)) {
      return AsyncValue.error(err, stack);
    }

    Error.throwWithStackTrace(err, stack);
  }
}