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

重试

借助 Effect 的重试策略增强韧性,用可自定义的重试策略与回退机制稳健地处理临时性失败。

在软件开发中,经常会遇到某次操作因网络问题、资源不可用或外部依赖等各种因素而临时失败的情况。此时,通常希望自动重试该操作,让它最终能够成功。

重试是处理临时性失败、确保关键操作成功执行的强大机制。在 Effect 中,借助内置函数与调度策略,重试变得简单而灵活。

在本指南中,我们将探讨 Effect 中重试的概念,并学习如何使用 retryretryOrElse 函数来处理失败场景。我们还会看到如何使用 Schedule 定义重试策略,由它决定何时重试以及重试多少次。

无论你处理的是网络请求、数据库交互,还是其他任何容易出错的操作,掌握 Effect 的重试能力都能显著提升应用程序的韧性与可靠性。

retry

Effect.retry 函数接收一个 effect 和一个 Schedule 策略,并在该 effect 失败时按照策略的规则自动重试。

如果该 effect 最终成功,就会返回其结果。

如果重试次数用尽而该 effect 仍然失败,则会把该失败传播出去。

这在处理间歇性失败时很有用,例如网络问题或临时的资源不可用。通过定义重试策略,你可以控制重试次数、重试之间的延迟,以及何时停止重试。

示例(以固定延迟重试)

import { Effect, Schedule } from "effect"

let count = 0

// Simulates an effect with possible failures
const task = Effect.async<string, Error>((resume) => {
  if (count <= 2) {
    count++
    console.log("failure")
    resume(Effect.fail(new Error()))
  } else {
    console.log("success")
    resume(Effect.succeed("yay!"))
  }
})

// Define a repetition policy using a fixed delay between retries
const policy = Schedule.fixed("100 millis")

const repeated = Effect.retry(task, policy)

Effect.runPromise(repeated).then(console.log)
/*
Output:
failure
failure
failure
success
yay!
*/

立即重试 n 次

你也可以用一个更简单的、立即重试的策略,让失败的 effect 重试固定次数:

示例(最多重试任务 5 次)

import { Effect } from "effect"

let count = 0

// Simulates an effect with possible failures
const task = Effect.async<string, Error>((resume) => {
  if (count <= 2) {
    count++
    console.log("failure")
    resume(Effect.fail(new Error()))
  } else {
    console.log("success")
    resume(Effect.succeed("yay!"))
  }
})

// Retry the task up to 5 times
Effect.runPromise(Effect.retry(task, { times: 5 }))
/*
Output:
failure
failure
failure
success
*/

基于条件重试

你可以通过指定条件来自定义重试的管理方式。使用 untilwhile 选项来控制何时停止重试。

示例(重试直到满足特定条件)

import { Effect } from "effect"

let count = 0

// Define an effect that simulates varying error on each invocation
const action = Effect.failSync(() => {
  console.log(`Action called ${++count} time(s)`)
  return `Error ${count}`
})

// Retry the action until a specific condition is met
const program = Effect.retry(action, {
  until: (err) => err === "Error 3",
})

Effect.runPromiseExit(program).then(console.log)
/*
Output:
Action called 1 time(s)
Action called 2 time(s)
Action called 3 time(s)
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: { _id: 'Cause', _tag: 'Fail', failure: 'Error 3' }
}
*/
Alternative

如果你的重试条件基于成功的结果而不是错误,也可以使用 Effect.repeat

retryOrElse

Effect.retryOrElse 函数会按照一个确定的 Schedule 策略,尝试对失败的 effect 重试多次。

如果重试次数用尽而该 effect 仍然失败,它就会改为运行一个回退 effect。

当你想在反复失败之后指定一个替代操作,从而优雅地处理失败时,这个函数很有用。

示例(带回退的重试)

import { Effect, Schedule, Console } from "effect"

let count = 0

// Simulates an effect with possible failures
const task = Effect.async<string, Error>((resume) => {
  if (count <= 2) {
    count++
    console.log("failure")
    resume(Effect.fail(new Error()))
  } else {
    console.log("success")
    resume(Effect.succeed("yay!"))
  }
})

// Retry the task with a delay between retries and a maximum of 2 retries
const policy = Schedule.addDelay(Schedule.recurs(2), () => "100 millis")

// If all retries fail, run the fallback effect
const repeated = Effect.retryOrElse(
  task,
  policy,
  // fallback
  () => Console.log("orElse").pipe(Effect.as("default value")),
)

Effect.runPromise(repeated).then(console.log)
/*
Output:
failure
failure
failure
orElse
default value
*/