错误处理

本章节探讨一些常用的检测和处理错误的方法。

Either

您可以使用  ZIO#either 来处理故障,方法是使用  ZIO[R, E, A]  并产生  ZIO[R, Nothing, Either[E, A]]

val zeither: UIO[Either[String, Int]] = 
  IO.fail("Uh oh!").either

您可以使用  ZIO.absolve 来将(Either 类型的)失败反转成 ZIO,这与上例相反,它将  ZIO[R, Nothing, Either[E, A]] 转换为 ZIO[R, E, A]

def sqrt(io: UIO[Double]): IO[String, Double] =
  ZIO.absolve(
    io.map(value =>
      if (value < 0.0) Left("Value must be >= 0.0")
      else Right(Math.sqrt(value))
    )  // UIO[Either[String, Int]] 
  )

捕获所有错误

如果您想捕获所有类型的错误并有效地尝试恢复,则可以使用 catchAll 方法:

val z: IO[IOException, Array[Byte]] = 
  openFile("primary.json").catchAll(_ => 
    openFile("backup.json"))

在传递给 catchAll的回调中,您可以返回具有不同错误类型(或可能为Nothing)的 effect,并且它将被反映在 catchAll 返回的 effect 类型中。

捕获特定的错误

如果您只想捕获并恢复某些类型的异常,则可以使用 catchSome 方法:

val data: IO[IOException, Array[Byte]] = 
  openFile("primary.data").catchSome {
    case _ : FileNotFoundException => 
      openFile("backup.data")
  }

于 catchAll 不同, catchSome 不能减少或消除所能包含的错误类型,但是它可以将错误类型扩展为更广泛的错误类别。

失败回退

您可以使用 orElse 组合器让它尝试一种 effect,如果它失败了,那么继续尝试执行另一个 effect。

val primaryOrBackupData: IO[IOException, Array[Byte]] = 
  openFile("primary.data").orElse(openFile("backup.data"))

Folding

Scala 的 Option 和 Either 数据类型都具有 fold 方法,它可以让您同时处理成功和失败。ZIO  effect 也具有类似的几种方式可以让您处理失败和成功。

第一种方法是使用 fold,通过为每种情况提供一个非效果的函数,让您以 “非效果” 的方式同时处理成功和失败:

lazy val DefaultData: Array[Byte] = Array(0, 0)

val primaryOrDefaultData: UIO[Array[Byte]] = 
  openFile("primary.data").fold(
    _    => DefaultData,
    data => data)

第二种方式是使用 foldM 函数,它允许您通过为每种情况提供效果化的(纯净的)处理程序,来处理失败和成功:

val primaryOrSecondaryData: IO[IOException, Array[Byte]] = 
  openFile("primary.data").foldM(
    _    => openFile("secondary.data"),
    data => ZIO.succeed(data))

几乎所有错误处理方法都是根据 foldM 定义的,因为它既强大又快速。

在下面的例子中,foldM 被同时用于处理 readUrls 方法的成功和失败的:

val urls: UIO[Content] =
  readUrls("urls.json").foldM(
    error   => IO.succeed(NoContent(error)), 
    success => fetchContent(success)
  )

重试

ZIO数据类型上有许多有用的方法可以重试失败的 effect。

ZIO#retry 是其中最基本的用法,它接收一个 Schedule 并返回一个会在前次执行失败后,基于指定的策略尝试重新执行的 effect:

import zio.clock._

val retriedOpenFile: ZIO[Clock, IOException, Array[Byte]] = 
  openFile("primary.data").retry(Schedule.recurs(5))

另一个强大的函数是 ZIO#retryOrElse,它允许指定一个失败回退函数,如果效果指定的策略也无法取得成功,则执行回退函数:

  openFile("primary.data").retryOrElse(
    Schedule.recurs(5), 
    (_, _:Long) => ZIO.succeed(DefaultData)
)

最后,ZIO#retryOrElseEither 函数允许返回失败回退函数返回一个不同的类型:

openFile("primary.data").retryOrElseEither(
    Schedule.once,
    (_, _:Unit) => ZIO.succeed("Return goes to Left")
)

有关如何新建一个 schedules,请参考 Schedule.

Next Steps

如果您对基本的错误处理感到满意,那么下一步就是学习安全的 资源管理

Leave a Reply
Your email address will not be published.
*
*

BACK TO TOP