Freezed Result

一个感觉像 Freezed union 的 Result<Success, Failure>。它代表了一个可能成功或失败的操作的输出。它持有类型为 Successvalue 或类型为 Failureerror

Failure 可以是任何类型,它通常代表比仅 ErrorException 更高的抽象。使用 Freezed Union 作为 Failure 的类型(例如 AuthFailure)非常普遍,其中包含表示不同错误类型的 cases(例如 AuthFailure.networkAuthFailure.storageAuthFailure.validation)。

因此,我们让 Result 的行为有点像 Freezed union(它有 when(success:, failure:))。基础类是由 Freezed 生成的,然后我们删除了不适用的部分(maybe*)并调整了其他部分(map*)使其感觉更像 Result。我们将在下面详细介绍。

用法

Result 交互的 3 种主要方式是:处理它、创建它和转换它。

处理值和错误

使用 when 通过处理 successfailure 两种情况来处理值。这是首选方式,因为你显式地处理了两种情况。

final result = fetchPerson(12);
result.when(
  success: (person) => state = MyState.personFound(person);
  failure: (error) => state = MyState.error(error);
);

或者使用 when 从两种情况创建一种通用类型。

final result = fetchPerson(12);
final description = result.when(
  success: (person) => 'Found Person ${person.id}';
  failure: (error) => 'Problem finding a person.';
);

或者忽略错误,使用 maybeValue 处理,它在失败时返回 null

final person = result.maybeValue;
if (person != null) {}

或者仅使用 outcome 来忽略值和错误。

if (result.isSuccess) {}
// elsewhere
if (result.isFailure) {}

或者使用 valueOrThrow 抛出 failure 情况并返回 success 情况。

try {
  final person = result.valueOrThrow();
} on ApiFailure catch(e) {
  // handle ApiFailure
}

创建 Result

使用命名构造函数 Result.successResult.failure 来创建结果。

Result.success(person)
Result.failure(AuthFailure.network())

使用类型化变量或函数返回类型声明 SuccessFailure 类型。

Result<Person, AuthFailure> result = Result.success(person);
Result<Person, AuthFailure> result = Result.failure(AuthFailure.network());

Result<Person, FormatException> parsePerson(String json) {
  return Result.failure(FormatException());
}

Result 作为 async 操作的返回值非常有用。

Future<Result<Person, ApiFailure>> fetchPerson(int id) async {
  try {
    final person = await api.getPerson(12);
    return Result.success(person);
  } on TimeoutException {
    return Result.failure(ApiFailure.timeout());
  } on FormatException {
    return Result.failure(ApiFailure.invalidData());
  }
}

有时你有一个可能出错但成功时返回 void 的函数。变量不能是 void,所以使用 Nothing。单例实例是 nothing

Result<Nothing, DatabaseError> vacuumDatabase() {
  try {
    db.vacuum();
    return Result.success(nothing);
  } on DatabaseError catch(e) {
    return Result.failure(e);
  }
}

你可以使用 catching 从闭包的返回值创建 success 结果。与构造函数不同,你需要 await 此调用的返回值。

在没有显式类型参数的情况下,闭包抛出的任何 Object 都将被捕获并作为 failure 结果返回。

final Result<String, Object> apiResult = await Result.catching(() => getSomeString());

使用类型参数时,只有指定的类型会被捕获。其余的将未被捕获地通过。

final result = await Result.catching<String, FormatException>(
  () => formatTheThing(),
);

转换 Result

根据需要处理并将此 Result 转换为另一个 Result

map

当 Result 是 success 时更改类型和值。当它是 failure 时,将错误保持不变。最适用于在永不失败的步骤的管道中转换 success 数据。

Result<DateTime, ApiFailure> bigDay = fetchPerson(12).map((person) => person.birthday);

mapError

当 Result 是 failure 时更改错误。当它是 success 时,将值保持不变。最适用于将低级异常转换为更抽象的失败类,这些类对异常进行分类。

Result<Person, ApiError> apiPerson(int id) {
  final Result<Person, DioError> raw = await dioGetApiPerson(12);
  return raw.mapError((error) => _interpretDioError(error));
}

mapWhen

一步更改错误和值。很少使用。

Result<Person, DioError> fetchPerson(int id) {
  // ...
}
Result<String, ApiFailure> fullName = fetchPerson(12).mapWhen(
    success: (person) => _sanitize(person.firstName, person,lastName),
    failure: (error) => _interpretDioError(error),
);

mapToResult

使用此方法将 success 转换为另一个 success 或兼容的 failure。最适用于使用可能失败的另一个操作来处理 success 值。

final Result<Person, FormatError> personResult = parsePerson(jsonString);
final Result<DateTime, FormatError> bigDay = personResult.mapToResult(
  (person) => parse(person.birthDateString),
);

解析 Person 可能会成功,但解析 DateTime 可能会失败。在这种情况下,初始的 success 会被转换为 failure。对于来自 Swift 的新人,也别名为 flatMap

mapErrorToResult

使用此方法将 error 转换为 success 或另一个 error。最适用于从具有解决方法的可恢复错误中恢复。

在此,mapErrorToResult 用于忽略可以通过缓存查找解决的错误。当所需值存在于本地缓存中时,初始的 failure 会被转换为 success_getPersonCache 函数还将无法恢复的原始 DioError 和访问缓存时发生的任何内部错误都转换为更通用的 FetchError

final Result<Person, DioError> raw = await dioGetApiPerson(id);
final Result<Person, FetchError> output = raw.mapErrorToResult((error) => _getPersonCache(id, error));

Result<Person, FetchError> _getPersonCache(int id, DioError error) {
  // ...
}

对于 Swift 新人,也别名为 flatMapError

mapToResultWhen

很少使用。这允许单个操作在 success value 上执行另一个可能以新方式失败并具有新 error 类型的操作,并且可以通过 success 来恢复任何原始 error 或将错误转换为新的 Failure 类型。

Result<Person, DioError> fetchPerson(int id) {
  // ...
}
Result<String, ProcessingError> fullName = fetchPerson(12).mapToResultWhen(
    success: (person) => _fullName(person.firstName, person,lastName),
    failure: (dioError) => _asProcessingError(dioError),
);

别名为 flatMapWhen,尽管 Swift 没有这个等价项。

替代方案

  • Result 大部分匹配 Swift 的 Result 类型。
  • result_type 完全匹配 Swift,也匹配部分 Rust。
  • fluent_result 允许错误失败时有多个错误,并允许通过扩展 ResultError 类来定义自定义错误。
  • Dartz 是一个函数式编程包,其 Either 类型可用作 Result 的替代品。它没有成功和失败的概念。相反,它使用 leftright。它使用函数式名称 fold 来完成我们用 when 做的事情。

GitHub

查看 Github