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

回退

了解在 Effect 程序中处理失败并实现回退机制的各种技术。

本页讲解 Effect 库中处理失败、构建回退机制的各种技术。

orElse

Effect.orElse 允许你先尝试运行一个 effect;如果它失败了,你可以改为提供一个回退 effect 来运行。

当你为第一个 effect 定义了备选方案、以便在它出错时优雅地处理失败时,这一点很有用。

示例(用 Effect.orElse 处理回退)

import { Effect } from "effect"

const success = Effect.succeed("success")
const failure = Effect.fail("failure")
const fallback = Effect.succeed("fallback")

// Try the success effect first, fallback is not used
const program1 = Effect.orElse(success, () => fallback)
console.log(Effect.runSync(program1))
// Output: "success"

// Try the failure effect first, fallback is used
const program2 = Effect.orElse(failure, () => fallback)
console.log(Effect.runSync(program2))
// Output: "fallback"

orElseFail

Effect.orElseFail 允许你用一个自定义的失败值替换某个 effect 的失败。如果该 effect 失败了,你可以提供一个新的失败,用它替代原本的失败返回。

这个函数只对失败的 effect 生效。如果该 effect 成功了,它不会受到任何影响。

示例(用 Effect.orElseFail 替换失败)

import { Effect } from "effect"

const validate = (age: number): Effect.Effect<number, string> => {
  if (age < 0) {
    return Effect.fail("NegativeAgeError")
  } else if (age < 18) {
    return Effect.fail("IllegalAgeError")
  } else {
    return Effect.succeed(age)
  }
}

const program = Effect.orElseFail(validate(-1), () => "invalid age")

console.log(Effect.runSyncExit(program))
/*
Output:
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: { _id: 'Cause', _tag: 'Fail', failure: 'invalid age' }
}
*/

orElseSucceed

Effect.orElseSucceed 允许你用成功值替换某个 effect 的失败。如果该 effect 失败了,它会改为以提供的值成功,从而确保该 effect 总是成功完成。

当你想保证无论原本的 effect 是否失败都能得到一个成功结果时,这一点很有用。

该函数确保任何失败都被有效地「吞掉」,并由一个成功值替代;在失败时提供默认值的场景下,这会很有帮助。

这个函数只对失败的 effect 生效。如果该 effect 已经成功,它将保持不变。

示例(用 Effect.orElseSucceed 把失败替换为成功)

import { Effect } from "effect"

const validate = (age: number): Effect.Effect<number, string> => {
  if (age < 0) {
    return Effect.fail("NegativeAgeError")
  } else if (age < 18) {
    return Effect.fail("IllegalAgeError")
  } else {
    return Effect.succeed(age)
  }
}

const program = Effect.orElseSucceed(validate(-1), () => 18)

console.log(Effect.runSyncExit(program))
/*
Output:
{ _id: 'Exit', _tag: 'Success', value: 18 }
*/

firstSuccessOf

Effect.firstSuccessOf 允许你按顺序依次尝试多个 effect,其中任何一个一旦成功,就会返回该结果。如果所有 effect 都失败,则返回列表中最后一个 effect 的错误。

当你有若干备选方案,并想使用第一个可行的方案时,这一点很有用。

这个函数是顺序执行的:可迭代对象中的各个 Effect 值会依次执行,第一个成功的 Effect 值将决定最终 Effect 值的结果。

空集合错误

如果传给 Effect.firstSuccessOf 函数的集合为空,它将抛出一个 IllegalArgumentException 错误。

示例(用多级回退查找配置)

在这个示例中,我们尝试从不同的节点获取配置。如果主节点失败,就回退到其他节点,直到找到一份成功的配置。

import { Effect, Console } from "effect"

interface Config {
  host: string
  port: number
  apiKey: string
}

// Create a configuration object with sample values
const makeConfig = (name: string): Config => ({
  host: `${name}.example.com`,
  port: 8080,
  apiKey: "12345-abcde",
})

// Simulate retrieving configuration from a remote node
const remoteConfig = (name: string): Effect.Effect<Config, Error> =>
  Effect.gen(function* () {
    // Simulate node3 being the only one with available config
    if (name === "node3") {
      yield* Console.log(`Config for ${name} found`)
      return makeConfig(name)
    } else {
      yield* Console.log(`Unavailable config for ${name}`)
      return yield* Effect.fail(new Error(`Config not found for ${name}`))
    }
  })

// Define the master configuration and potential fallback nodes
const masterConfig = remoteConfig("master")
const nodeConfigs = ["node1", "node2", "node3", "node4"].map(remoteConfig)

// Attempt to find a working configuration,
// starting with the master and then falling back to other nodes
const config = Effect.firstSuccessOf([masterConfig, ...nodeConfigs])

// Run the effect to retrieve the configuration
const result = Effect.runSync(config)

console.log(result)
/*
Output:
Unavailable config for master
Unavailable config for node1
Unavailable config for node2
Config for node3 found
{ host: 'node3.example.com', port: 8080, apiKey: '12345-abcde' }
*/