已发布 上游基线 bf46254 原文 ↗ 在 GitHub 编辑

Result

用 Result 数据类型把互斥的取值表示为 Success 或 Failure,从而在计算中实现精确的控制流。

Result 数据类型表示两种互斥的取值:一个 Result<A, E> 要么是 Success 值,要么是 Failure 值,其中 ASuccess 值的类型,EFailure 值的类型。

理解 Result 与 Exit

Result 主要用作简单的可辨识联合(discriminated union),对于需要详细错误信息的操作,并不推荐把它当作主要的结果类型。

在 Effect 中,Exit 才是首选的结果类型,用于捕获关于失败的完整细节。 它封装 effectful 计算的结果,区分成功以及各种失败模式,例如错误、defect 与中断。

创建 Result

你可以使用 Result.succeedResult.fail 这两个构造器创建 Result

Result.succeed 创建一个类型为 ASuccess 值。

示例(创建一个 Success 值)

import { Result } from "effect"

const successValue = Result.succeed(42)

console.log(successValue)
successValue // => Result.succeed(42)

Result.fail 创建一个类型为 EFailure 值。

示例(创建一个 Failure 值)

import { Result } from "effect"

const failureValue = Result.fail("not a number")

console.log(failureValue)
failureValue // => Result.fail("not a number")

Guards

使用 Result.isFailureResult.isSuccess 检查一个 ResultFailure 值还是 Success 值。

示例(用 Guards 检查 Result 的类型)

import { Result } from "effect"

const foo = Result.succeed(42)

if (Result.isFailure(foo)) {
  console.log(`The failure value is: ${foo.failure}`)
} else {
  console.log(`The Success value is: ${foo.success}`)
  foo.success // => 42
}
// Output: "The Success value is: 42"

模式匹配

使用 Result.match 处理 Result 的两种情况:分别为 FailureSuccess 指定独立的回调。

示例(对 Result 进行模式匹配)

import { Result } from "effect"

const foo = Result.succeed(42)

const message = Result.match(foo, {
  onFailure: (failure) => `The failure value is: ${failure}`,
  onSuccess: (success) => `The Success value is: ${success}`,
})

console.log(message)
message // => "The Success value is: 42"

映射

映射 Success 值

使用 Result.map 转换 ResultSuccess 值。你提供的函数只会作用于 Success 值,Failure 值保持不变。

示例(转换 Success 值)

import { Result } from "effect"

// Transform the Success value by adding 1
const successResult = Result.map(Result.succeed(1), (n) => n + 1)
console.log(successResult)
successResult // => Result.succeed(2)

// The transformation is ignored for Failure values
const failureResult = Result.map(Result.fail("not a number"), (n) => n + 1)
console.log(failureResult)
failureResult // => Result.fail("not a number")

映射 Failure 值

使用 Result.mapError 转换 ResultFailure 值。所提供的函数只会作用于 Failure 值,Success 值保持不变。

示例(转换 Failure 值)

import { Result } from "effect"

// The transformation is ignored for Success values
const successResult = Result.mapError(Result.succeed(1), (s) => s + "!")
console.log(successResult)
successResult // => Result.succeed(1)

// Transform the Failure value by appending "!"
const failureResult = Result.mapError(
  Result.fail("not a number"),
  (s) => s + "!",
)
console.log(failureResult)
failureResult // => Result.fail("not a number!")

同时映射两个值

使用 Result.mapBoth 同时转换 ResultFailure 值与 Success 值。这个函数接受两个独立的转换函数:一个用于 Failure 值,另一个用于 Success 值。

示例(同时转换 Failure 值与 Success 值)

import { Result } from "effect"

const transformedSuccess = Result.mapBoth(Result.succeed(1), {
  onFailure: (s) => s + "!",
  onSuccess: (n) => n + 1,
})
console.log(transformedSuccess)
transformedSuccess // => Result.succeed(2)

const transformedFailure = Result.mapBoth(Result.fail("not a number"), {
  onFailure: (s) => s + "!",
  onSuccess: (n) => n + 1,
})
console.log(transformedFailure)
transformedFailure // => Result.fail("not a number!")

与 Effect 互操作

Result 实现了 Yieldable trait,因此可以直接在 Effect.gen 中 yield。若要把 Result 传给 Effect.all 这类 Effect 组合子,请用 Effect.fromResult 显式转换。

Result 如何映射到 Effect

Result 变体映射到的 Effect说明
Failure<E>Effect<never, E>表示失败
Success<A>Effect<A>表示成功

示例(把 ResultEffect 结合使用)

import { Effect, Result } from "effect"

// Function to get the head of an array, returning Result
const head = <A>(array: ReadonlyArray<A>): Result.Result<A, string> =>
  array.length > 0 ? Result.succeed(array[0]!) : Result.fail("empty array")

head([1, 2, 3]) // => Result.succeed(1)

// Simulated fetch function that returns Effect
const fetchData = (): Effect.Effect<string, string> => {
  const success = Math.random() > 0.5
  return success
    ? Effect.succeed("some data")
    : Effect.fail("Failed to fetch data")
}

// Result is not an Effect subtype - convert explicitly with Effect.fromResult
const program = Effect.all([Effect.fromResult(head([1, 2, 3])), fetchData()])

Effect.runPromise(program).then(console.log, console.error)
/*
Example Output:
[ 1, 'some data' ]
*/

组合两个或多个 Result

用 flatMap 与 map 组合

用提供的函数组合两个 Result 值:串联 Result.flatMapResult.map。这会创建一个新的 Result,其中保存两个原始 Result 值的组合值。

示例(把两个 Result 组合成一个对象)

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.succeed("John")
const maybeAge: Result.Result<number, string> = Result.succeed(25)

// Combine the name and age into a person object
const person = Result.flatMap(maybeName, (name) =>
  Result.map(maybeAge, (age) => ({
    name: name.toUpperCase(),
    age,
  })),
)

console.log(person)
person // => Result.succeed({ name: "JOHN", age: 25 })

如果任意一个 Result 值是 Failure,结果也会是 Failure,并保存最先遇到的 Failure 值:

示例(组合出含 Failure 值的结果)

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.succeed("John")
const maybeAge: Result.Result<number, string> = Result.fail("Oh no!")

// Since maybeAge is a Failure, the result will also be a Failure
const person = Result.flatMap(maybeName, (name) =>
  Result.map(maybeAge, (age) => ({
    name: name.toUpperCase(),
    age,
  })),
)

console.log(person)
/*
Output:
{ _id: 'Result', _tag: 'Failure', failure: 'Oh no!' }
*/

all

若要把多个 Result 值组合起来而不转换它们的内容,可以使用 Result.all。这个函数返回的 Result 结构与输入一致:

  • 如果传入元组,结果就是等长的元组。
  • 如果传入 struct,结果就是包含相同键的 struct。
  • 如果传入 Iterable,结果就是数组。

示例(把多个 Result 组合成元组与 struct)

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.succeed("John")
const maybeAge: Result.Result<number, string> = Result.succeed(25)

//      ┌─── Result<[string, number], string>
//      ▼
const tuple = Result.all([maybeName, maybeAge])
console.log(tuple)
tuple // => Result.succeed(["John", 25])

//      ┌─── Result<{ name: string; age: number; }, string>
//      ▼
const struct = Result.all({ name: maybeName, age: maybeAge })
console.log(struct)
struct // => Result.succeed({ name: "John", age: 25 })

如果有一个或多个 Result 值是 Failure,则返回最先遇到的 Failure

示例(处理多个 Failure 值)

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.fail("name not found")
const maybeAge: Result.Result<number, string> = Result.fail("age not found")

// The first Failure value will be returned
console.log(Result.all([maybeName, maybeAge]))
Result.all([maybeName, maybeAge]) // => Result.fail("name not found")

gen

Effect.gen 类似,Result.gen 提供了一种更具可读性的、基于生成器的语法来处理 Result 值,让涉及 Result 的代码更易编写和理解。这种方式与 async/await 类似,但专为 Result 量身定制。

示例(使用 Result.gen 创建一个组合值)

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.succeed("John")
const maybeAge: Result.Result<number, string> = Result.succeed(25)

const program = Result.gen(function* () {
  const name = (yield* maybeName).toUpperCase()
  const age = yield* maybeAge
  return { name, age }
})

console.log(program)
program // => Result.succeed({ name: "JOHN", age: 25 })

当序列中任意一个 Result 值是 Failure 时,生成器会立即返回该 Failure 值,并跳过后续操作:

示例(用 Result.gen 处理 Failure 值)

在这个示例中,Result.gen 一遇到 Failure 值就停止执行,从而在不再执行后续操作的情况下把错误传播出去。

import { Result } from "effect"

const maybeName: Result.Result<string, string> = Result.fail("Oh no!")
const maybeAge: Result.Result<number, string> = Result.succeed(25)

const program = Result.gen(function* () {
  console.log("Retrieving name...")
  const name = (yield* maybeName).toUpperCase()
  console.log("Retrieving age...")
  const age = yield* maybeAge
  return { name, age }
})

console.log(program)
/*
Output:
Retrieving name...
*/
program // => Result.fail("Oh no!")

这些示例中使用 console.log 仅用于演示目的。使用 Result.gen 时,请避免在生成器函数中引入副作用,因为 Result 应当保持为纯数据结构。