本章节探讨一些常用的检测和处理错误的方法。
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
如果您对基本的错误处理感到满意,那么下一步就是学习安全的 资源管理。