Freezed Result
一个感觉像 Freezed union 的 Result<Success, Failure>。它代表了一个可能成功或失败的操作的输出。它持有类型为 Success 的 value 或类型为 Failure 的 error。
Failure 可以是任何类型,它通常代表比仅 Error 或 Exception 更高的抽象。使用 Freezed Union 作为 Failure 的类型(例如 AuthFailure)非常普遍,其中包含表示不同错误类型的 cases(例如 AuthFailure.network、AuthFailure.storage、AuthFailure.validation)。
因此,我们让 Result 的行为有点像 Freezed union(它有 when(success:, failure:))。基础类是由 Freezed 生成的,然后我们删除了不适用的部分(maybe*)并调整了其他部分(map*)使其感觉更像 Result。我们将在下面详细介绍。
用法
与 Result 交互的 3 种主要方式是:处理它、创建它和转换它。
处理值和错误
使用 when 通过处理 success 和 failure 两种情况来处理值。这是首选方式,因为你显式地处理了两种情况。
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.success 和 Result.failure 来创建结果。
Result.success(person)
Result.failure(AuthFailure.network())
使用类型化变量或函数返回类型声明 Success 和 Failure 类型。
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的替代品。它没有成功和失败的概念。相反,它使用left和right。它使用函数式名称fold来完成我们用when做的事情。