STM 介绍

STM 介绍

简介 ZIO 支持软件事务性内存 (STM),它是一种模块化的可组合的并发性数据结构。它允许我们在一个原子事务中组合并执行一组内存操作。 STM是一组并发任务之间的通信的抽象。STM 的主要优点是可组合性和模块化。我们可以编写可以与同样使用 STM 构建的任何其他抽象组合的并发抽象,同时不必暴露我们的抽象是如何确保安全的细节。而锁定机制则通常不是这样的。 Transactional 操作的想法并不新鲜,它们一直是分布式系统的基础,也是那些数据库之所以能够保证我们拥有 ACID 性质。STM是纯内存操作的。所有的操作都发生在内存中,与远程系统或数据库无关。与 ACID 属性的数据库概念非常相似,但缺少持久性,因为那对内存中的操作没有意义。 在事务性内存中,我们有关于 ACID 属性的几个方面: Atomicity(原子性)——在写操作中,我们需要原子更新,这意味着更新操作要么应该立即运行,要么根本不运行。 Consistency(一致性)——在读操作中,我们希望程序状态具有一致的视图,以确保各部分都引用相同的状态,在获得状态时获得相同的值。 Isolated(隔离性)——如果我们有多个同步更新,我们需要在隔离的事务中执行这些更新。每个事务不会影响其他并发事务。无论有多少纤程在运行多少数量的事务,都不必担心其它事务中发生的事情。 ZIO STM API 的灵感来自 Haskell 的 STM库,尽管 ZIO 中的实现完全不同。 问题 让我们从一个简单的inc函数开始,它接受一个 Int 类型的可变引用 并增加其 amount: 如果只有一个纤程,那不会有问题。这个函数看上去是正确的。但是,如果在读取计数器的值和设置新值之间,另一个纤程出现并改变了计数器的值,会发生什么?在我们读取计数器之后,另一个纤程恰好更新了计数器。所以这个函数是有竞争条件的,我们可以用下面的程序来测试: 由于上述程序运行了 10 个并发纤程来增加计数器值。但是,我们不能期望这个程序总是返回结果 10。 为了解决这个问题,我们需要原子地执行 get 和 set 操作。所幸 Ref 数据类型的一些 API,比如 update,updateAndGet和modify 提供了读写同步的原子操作。 需要注意的是有关 modify 操作最重要的一点是它不使用悲观锁定。它不对临界区使用任何锁定原语。它对潜在的操作碰撞有一个乐观的假设。 该 modify 函数执行以下三个步骤: 它假设其他纤程在大多数情况下不会改变共享状态并且在多数情况下不会互相干扰。因此它在不使用任何锁定原语的情况下读取共享状态。 你应该为最坏的情况做好准备。如果另一个纤程同时接入,会发生什么?因此,当我们开始写入新值时,应该检查所有可能影响的方面。它应该确保看到全局一致的状态,如果它看到了,那么它才可以改变那个值。 如果它遇到不一致的值,则不应继续。它应该使以上假设变得无效并中止更新共享状态。然后基于被修改过的值重新尝试 modify 操作。 让我们看看在没有任何锁定机制的情况下 Ref 如何实现 modify 功能: 正如我们所看到的,modify 操作是根据 compare-and-swap 操作来实现的,它帮助我们以原子方式执行读取和更新。 让我们将 inc 函数重命名为以下的 deposit,尝试将钱从一个账户转移到另一个账户的经典问题: 增加 withdraw 函数: 看起来还不错,但是实际上我们要先检查账户中是否有足够的余额可以提现。所以让我们修改它添加一个不变量来检查: 如果在检查和更新余额之间,另一条纤程来提取账户中的资金怎么办?所以这个解决方案包含一个错误。它有可能让账户达到负平衡。 假设我们最终达成了一个解决方案来自动退出,但问题仍然存在。我们需要以原子的方式将 withdraw 和 deposit 组合在一起以创建 transfer函数: 在上面的例子中,即使我们假设 withdraw 和 deposit 各自是原子的,我们也不能组合这两个事务。它们在并发环境中会产生错误。这段代码不能给我们保证 withdraw 都 deposit 两者在同一个原子操作中执行。同时执行此 transfer 方法的其他纤程可以覆盖共享状态并引入竞争条件。 我们需要一个解决方案来原子地组合事务。这就是STM发挥作用的地方。 可组合的并发 软件事务内存为我们提供了一种组合多个事务并在一个事务中执行它们的方法。 让我们继续我们的最后努力,将我们的 withdraw 方法转换为一个原子操作。为了使用 STM 来解决问题,我们将 Ref 替换 TRef. TRef 的含义是Transactional Reference;它是 STM 世界中的可变引用。STM 是一个一元的数据结构,代表一个可以事务化执行的 effect: 同样 deposit 操作是原子的,但为了能够和 withdraw 组合,我们也需要用 TRef 将其重构并 return STM: 在 STM 的世界中,我们可以组合所有操作,并直到世界的尽头,我们才在一个操作中原子地执行所有这些操作。为了能够让 withdraw 和 deposit 组合在一起,我们需要让它们都保持在 STM 的世界中。因此,我们不对它们中的每一个单独执行STM.atomically 或 STM#commit 方法。 现在我们可以在 STM 的世界中组合这两个函数来定义 transfer,并将它们转换为单一原子的 IO 方法: 假设我们正在将资金从一个账户转移到另一个账户。如果我们取出第一个账户但没有存入第二个账户,这种中间状态对任何外部纤程都是不可见的。如果不存在任何有冲突的变更,则事务完全成功。如果存在任何冲突或冲突更改,则整个 STM 将被重试。 它是如何工作的 STM 使用了和 Ref#modify 函数相同的思想,但具有可组合性特征。STM 的主要目标是提供一种机制来组合多个事务并在一个原子操作中执行它们。 可组合部分背后的机制是显而易见的。STM 有自己的世界。它有很多有用的 combinators(组合子),例如 flatMap 和orElse 可用于组合多个 STM 并创建更优雅的结果。在我们通过 STM#commit 或 […]

Managed

Managed 是一个封装了资源的 acquire 和 release 的数据结构。 Managed[E, A] 表示一个类型为 A 的托管资源,它可以通过 use 方法被使用。资源将在使用之前自动获取资源,并在使用之后自动释放资源。 如果资源无法在 use 范围内生效,这意味着您可能在获得资源后,在 use 中将其浪费掉,然后在资源消耗完后再次使用它,根据资源提供的功能类型它可能已经不再有效,并且可能会因检查错误而失败。 在此示例中,Managed 类在调用 use 时创建队列,并在 doSomething 完成时调用 shutdown。 创建一个 Managed 如上例所示,可以通过传递 acquire 函数和 release 函数来创建 Managed。 也可以从 effect 中创建。在这种情况下,release 函数将不执行任何操作。 您也可以从纯值创建 Managed。 ZIO 环境中的 Managed Managed[E, A] 实际上是 ZManaged[Any, E, A] 的别名。如果您希望acquire,release 或 use 函数中使用环境 R,请使用 ZManaged 来代替 Managed。 合并 Managed 可以使用 flatMap 将多个 Managed 合并在一起,以得到获取和释放所有资源的单个 Managed。

IO

IO[E, A] 类型的值,是一个可能导致 E 类型的失效,或永远运行,或产生 A 类型成功值的 effect。 IO 的值是不可变的,并且所有的 IO 函数都会产生新的 IO 值,使得 IO 像其它普通的 Scala 不变数据结构一样可以被推理和使用。 IO 值实际上啥也不做;它们只是一个描述交互效果的模型的值。 IO 可以被 ZIO 运行时系统解释成与外部世界的交互效果。理想情况下,这个过程在应用程序的 main 函数中一次性发生的。 App 类自动提供了此功能。 纯值 您可以通过 IO.succeed 将一个纯值装载入 IO 绝对不要使用任何构造函数将不纯代码导入 IO。这样做的结果是不确定的。 不会失败的 IO UIO[A] 类型的 IO 值(它的错误类型为 Nothing)被认为是不会失败的,因为Nothing 类型表示不存在的,即,不能有 Nothing 类型的实际值。此类型的值可能会产生 A 类型的成功结果,但永远不会导致 E 类型的失败。 不产生有效值的 IO IO[E, Nothing] 类型的 IO 值(其中值类型为 Nothing)被认为是无有效值的,因为 Nothing 类型表示不存在的,即不能有 Nothing 类型的实际值。此类型的值可能会得到 E 类型的失败,但永远不会产生成功的结果值。 不纯的代码 您可以使用 IO 的 effectTotal 方法将同步效果的代码导入为纯函数程序: 它的执行结果有可能是任何 Throwable 类型的失败。 如果(这个结果的)范围太广,可以使用 ZIO 的 refineOrDie 方法仅保留对某些类型的异常的关注,而其他任何类型的异常则直接导致“死亡”: 您可以使用 IO 的 effectAsync 方法将异步效果的代码导入为纯函数程序: 在此示例中,假设 Http.req 方法在得到异步执行的结果后将调用指定的回调函数。 映射 您可以通过调用 map 方法时给予函数 A => B 来将 IO[E, A] 更改为 IO[E, B]。这可以将前一操作产生的值转换为另一个值。 您可以在调用 mapError 方法时使用函数 E => E2 ,将 IO[E, A] 转换为 IO[E2, A]: 链式调用 您可以使用 flatMap 方法依次执行两个操作。第二动作的执行取决于第一动作产生的值。 您可以使用 Scala 的 for comprehension 语法使这种类型的代码更紧凑: Brackets bracket 是内置原语,可让您安全地获取和释放资源。 Brackets 被用在类似 try/catch/finally 的场景,但是 brackets 可以被用于同步和异步,可以和纤程中断无缝配合。它基于不同的错误模型构建,以确保不会丢失任何错误。 Brackets 由一个获取方法,一个使用方法(用于使用获取的资源),和一个释放方法组成。 释放动作由运行时来保证执行,哪怕使用中抛出了异常或执行的纤程被中断。 Brackets 支持组合语义,因此,如果将一个 bracket 嵌套在另一个 bracket 内,如果外部 bracket 获取了资源,那么即使内部 bracket 的释放失败,外部 bracket 的释放动作也依然会得到执行。 有一个名为 ensuring 的方法提供了另一个类似 finally 的功能: 一个完整的使用 brackets 的例子

摘要

ZIO 包含以下数据类型,可以帮助您解决异步和并发编程中的复杂问题。 Fiber(纤程) – 纤程是对一个处于运行中的 IO 值的建模,它是一个绿色线程。 STM(Software Transactional Memory) – 软件事务性内存是一种支持事务运算并得到失败或成功的 effect。 ZIO – ZIO 是一个“效果化”的程序的模型,该程序可能失败或成功。 Managed – Managed 是一个值,它描述了一种可释放的,在给定范围内只能被消费一次的资源。 Promise – Promise 是一个变量模型,它可以被多个纤程共享但是只可以被设置一次。 Queue – Queue 是一个永不阻塞的异步队列,对于多个并发的生产者和使用者来说是安全的。 Ref – Ref[A] 对 A 类型的值的可变引用进行建模。它有两个基本的操作,set 将新值填充到 Ref 中,get 取回其当前内容。 Ref 上的所有操作都是原子和线程安全的,它为同步并发程序提供了可靠的基础。 FiberRef – FiberRef[A] 对 A 类型的值的可变引用进行建模。但与 Ref[A] 不同的是,该值仅能与当前执行中的 Fiber 绑定。您可以将其视为类似于 Java 的 ThreadLocal。 Schedule – Schedule 是一个可重复任务的模型,它可被用于重复执行成功的或重试失败的 IO 值。 Semaphore – 信号量 Semaphore  是一个可与 ZIO 中断配合使用的异步(非阻塞)信号量。 Chunk – ZIO Chunk: 一个高效的,纯的 Arrays 的替代方案。 TArray – TArray[A] 是可以参与事务运算的的可变数组。 TMap – A TMap[A] 是可以参与事务运算的的可变 map TPriorityQueue – A TPriorityQueue[A] 是可以参与事务的可变优先级队列。 TPromise – A TPromise 可参与事务运算的,只可以被设置一次的 Promise 可变量。 TQueue – A TQueue 是可以参与事务运算的可变队列。 TRef – A TRef 是一个可以参与事务运算的,对不可变量的可变参考。 TSet – A TSet 是可以参与事务的可变集合。 Has –  Has 用于表示一个 effect 对 A 类型服务的依赖关系。 ZLayer – A ZLayer 用于描述应用程序的一个“层”。 除了这些核心数据类型外,还可以在 ZIO streams 库中找到以下数据类型: Sink — Sink 是 Stream 中的数据消费者的,当它从 Stream 中消费得数据后可能会返回一个值。 Stream — Stream 是一个惰性的并发异步数据源。 要了解有关这些数据类型的更多信息,请浏览上面的页面,或查看 Scaladoc 文档。

平台

ZIO在最大程度上提供了跨平台的一致接口,从而允许开发人员编写一次代码并将其部署到任何地方。但是,要注意的平台之间存在一些不可避免的差异。 JVM ZIO支持 Java 版本 8 和更高版本以及 Scala 版本 2.11、2.12、2.13 和 Dotty。 在JVM上,Blocking 服务可用于将 effect 锁定在阻塞线程池,它已经包含在 ZEnv中。有关阻塞同步副作用的进一步讨论,请参见关于 创建 Effects 的文档。 Scala.js ZIO 支持 Scala.js 1.0. 尽管 ZIO 是零依赖的库,但这是建立在假设平台具有一些基本功能的基础上。特别是,由于 Scala.js 中缺少某些 java.time 方法的实现,因此用户必须自定义自己的java.time 依赖关系。 ZIO在其自己的内部测试套件中使用的是 scala-java-time。可以将其添加为依赖项,如下所示: 由于其单线程执行模型,Scala.js 不支持阻塞操作。因此,Blocking 服务不可用,也不包含在 ZEnv 中。另外,有一些方法 Scala.js 或者不支持,或者它们是不安全: 不支持 Console 服务中的 readLine 方法,因为 Scala.js 没有使用 Scala 标准库实现从控制台上阻塞读取一行输入和其下的方法。 运行时上的 unsafeRun,unsafeRunTask 和 unsafeRunSync 方法是不安全。所有这些方法均会同步将值返回并且可能阻塞,如果 effect 中包含异步步骤,包括运行时为保证执行公平性导入的摆出点,用户应改用unsafeRunAsync,unsafeRunAsync_ 或 unsafeRunToFuture方法。 Scala Native 目前对 Scala Native 的支持尚处于试验阶段。当支持 Scala Native 平台时,将添加更多详细信息。

性能

zio 拥有出色的性能,具有手动优化的低级解释器,该解释器可实现对右相关联的绑定的零分配,和最小化的左相关联的绑定的分配。 Monix Task 和 Cats IO:benchmarks 项目可用于将 IO(特指 ZIO) 与其它 Monad effect ,包括“Future”(虽然它不是Monad effect,但可供参考),Monix Task 和 Cats IO 进行比较。 截至撰写本文时,IO显著地快于或至少与所有其他纯函数解决方案可相提并论。

背景知识

过程式程序中使用到的过程函数包括: Partial(偏函数) — 该函数不包含对某些输入的返回处理(例如,可能因为无法处理的输入导致异常)。 Non-Deterministic(非确定性函数) — 该函数可能为相同的输入返回不同的输出。 Impure(非纯函数) — 该函数或者会产生副作用,或使用可变量或访问外部值。 与过程式程序不同,函数式程序只使用纯函数,它包含以下特征: Total(全函数) — 函数总是为每一种可能的输入返回一个输出。 Deterministic(确定性函数) — 函数总是为相同的输入返回相同的输出。 Pure(纯函数) — 函数的唯一效果是完全根据输入来决定输出。 纯函数仅以完全确定的方式将输入值组合或转换为输出值。纯函数更易于理解,更易于测试,更易于重构和更易于抽象。 函数式程序不会直接与外部世界交互,因为这样将会带来偏向性,不确定性和副作用。相反,函数式程序会构建并返回数据结构,并通过该数据结构描述(或建模)与现实世界的交互。 用于建模该过程 effect 的不可变数据结构称为函数式 effect。这个概念对于深入了解 ZIO 的工作原理至关重要,将在下一节中介绍。 程序就是值 我们可以仅用三个指令来构建一个描述控制台程序的数据结构: 在此模型中,Console[A]是一个不可变的,值类型安全的,返回 A 类型值的控制台程序。 下面这个 Console 数据结构是一个有序的“(语法)树”,在它“结尾”处,您会看到一条 Return 指令,该指令包含一个类型为 A 的值,该值是 Console[A] 程序的返回值。 尽管非常简单,但此数据结构足以构建交互式程序: 这个不变量值没有做任何事情——它只是描述一个程序,该程序打印出一条消息,请求输入,然后打印出另一条取决于输入的消息。 尽管此程序只是一个模型,但我们可以使用解释器将模型转换为过程效果,解释器将在数据结构上递归,将每条指令翻译为它描述的副作用: 解释(也称为运行或执行)不是函数式的,因为它可能是偏函数式的,非确定性的或不纯的。在理想的应用程序中,解释只需发生一次:在应用程序的主函数中。而该应用程序的其余部分应该完全是纯函数式的。 实际上,直接使用构造函数构建控制台程序不是很方便。相反,我们可以定义辅助函数,这些辅助函数看起来更像它们的 effect 等效物: 如果我们在 Console 上定义 map 和 flatMap 方法,则将这些“叶子”指令组合成较大的程序将变得更加容易: map 方法可通过提供函数 A => B 将返回 A 的 console 程序转换为返回 B 的控制台程序。 flatMap 方法使您可以按顺序将一个通过回调返回 A 类型结果的控制台程序和另一个将 A 作为输入的控制台程序组合在一起。 这两种函数的定义如下: 借助这些 map 和 flatMap 方法,我们现在可以利用 Scala 的  for comprehensions,编写看上去类似过程式的等效程序: 当我们希望执行该程序时,我们只需要在 Console 上调用解释器即可。 所有的函数式 Scala 程序都是这样构造的:与其直接与现实世界进行交互,不如建立一个函数式的 effect,这无非是一个对过程进行建模的不可变的,类型安全的,类似树的数据结构。 函数式程序员使用函数 effect 来构建复杂的现实世界软件,同时,不放弃纯函数式编程所提供的等式推理,可组合性和类型安全性。 Next Steps 如果函数式 effect 对您来说开始变得更有意义,那么下一步就是进一步了解 ZIO 中的核心效果类型。

基本并发处理

ZIO 的底层使用了 纤程 来支持多并发。 纤程非常强大,但是它们运行在底层,为了提高效率,ZIO 提供了基于纤程的上层操作。 如果可能,你应该总是选择使用上层操作,而不是直接和纤程打交道。为了完整起见,本节将介绍两种纤程以及在其上构建的一些上层操作。 纤程 Fibers ZIO 的并发是构建在 纤程 上的,它是一种由 ZIO 运行时系统实现的轻量级的“绿色线程”。 和操作系统线程不同,纤程几乎不占用内存,具有可伸缩的堆栈,不会浪费资源,并且如果纤程处于挂起和无法访问状态,则会自动被垃圾回收。 纤程是由 ZIO 运行时调度的,并且会相互协作产生,即使在单线程环境(例如JavaScript,甚至是只配置一个线程的 JVM)中运行时,也可以实现多任务处理。 ZIO 中的所有效果都是由某个纤程执行的。如果你没有创建过纤程,那么纤程将由正在当前的操作(如果该操作是并发的或并行的)或由 ZIO 运行时系统自动创建。 即使您仅编写“单线程”代码,没有并行或并发操作,也将至少有一个纤程,作为执行 effect 的“主”纤程。 Fiber 数据类型 每条 ZIO 纤程都负责执行着某个 effect,并且由 ZIO 的 Fiber 数据类型来代表该运行计算的“句柄”。Fiber 数据类型于 Scala 的 Future 数据类型很类似。 Fiber[E, A] 数据类型在 ZIO 中有两个参数: E Failure Type. 如果纤程运行失败,将返回这种类型的值。 A Success Type. 如果纤程运行成功,则返回这种类型的值。 纤程并没有代表环境类型的 R 参数,以为它们执行的 effects 已经处于运行状态了,并且也已经为他们提供了所需的环境。 效果分支 创建纤程的最基本方法是对一个已存在的 effect 执行 fork 操作。 从概念上讲,效果分叉后会在新的纤程上开始执行,并且返回对新创建的 Fiber 具柄。 下面的代码创建了一个纤程来执行 fib(100): Joining Fibers 从 Fiber 中返回一个 effect 的方法之一,是使用 Fiber#join。由方法 Fiber#join 返回的纤程可以是成功的,也可以是失败的: Awaiting Fibers 另一种纤程返回的方法是 Fiber#await,它返回一个包含返回值的 effect,该值提供了有关纤程如何完成的完整信息。 纤程的中断 可以中断不再需要结果的纤程从而立即终止纤程的执行,安全地释放所有资源并运行所有的终结器。 和 await 一样, Fiber#interrupt 返回一个纤程如何完成退出的说明。 根据设计,由 Fiber#interrupt 返回的 effect 将不可恢复直到纤程彻底完成后。如果不需要恢复它,则可以对中断本身使用 fork: 纤程的组合 ZIO 允许您使用  Fiber#zip 或 Fiber#zipWith 来组合纤程。 这些方法将两个纤程合并为一个单个的纤程,并产生两者的结果。如果任一个纤程发生失败,则合成的纤程也将失败。 另一个组合纤程的方法是使用 Fiber#orElse 函数。如果第一个纤程成功,那么组合将返回该成功值,否则组合将执行并返回第二个纤程的返回值。(无论是否成功) 并行 ZIO 提供了很多操作用于实现 effect 的并行。这些方法都以 Par 作为后缀以有助于您判断何时将采用并行。 例如, 普通的 ZIO#zip 方法将两个 effect 按序列运算并合并在一起。但是还有一个 ZIO#zipPar 方法,可以将两个 effect 并行执行并合并在一起。 下表总结了一些顺序操作及其对应的并行版本: Description Sequential Parallel Zips two effects into one ZIO#zip ZIO#zipPar Zips two effects into one ZIO#zipWith ZIO#zipWithPar Collects from many effects ZIO.collectAll ZIO.collectAllPar Effectfully loop over values ZIO.foreach ZIO.foreachPar Reduces many values ZIO.reduceAll ZIO.reduceAllPar Merges many values ZIO.mergeAll ZIO.mergeAllPar 对于所有并行操作,如果一个 effect 失败,则其他 effect 也将被中断,以最大程度地减少不必要的计算。 如果不希望这种快速失败的行为,则可以先使用 ZIO#either 或 ZIO#option […]

资源管理

本章节探讨使用 ZIO 来管理资源的通常的方法。 ZIO的资源管理功能包含了同步,异步,并发和其他 effect 类型,即使在应用程序出现故障,中断或缺陷的情况下,也能提供有力的保证。 Finalizing 通过  ZIO#ensuring  方法,ZIO 提供了类似 try / finally 的机制。 类似于 try / finally 一样, ensuring 函数可确保“终结器(finalizer)”将在一个 effect 开始执行然后终止(无论出于何种原因)后开始执行。 终结器是不允许失败的,这意味着它必须在内部处理所有的错误。 和 try / finally 一样,终结器可以嵌套,并且任何来自内部终结器的错误不会影响到它的外部终结器。嵌套终结器的执行顺序是反向,并且线性的(不是并行的)。 和 try / finally 不同的是,ensuring 可以工作于所有的 effect 类型,包括异步和并发 effect。 Bracket 通常 try / finally 被用于安全地获取和释放资源,例如一个新的 socket 的连接或一个文件的打开: ZIO 将这种通用模式封装在 ZIO#bracket 中,它允许您自动一个acquire effect 用于获取资源;一个 release effect 用于释放它;和一个 use effect 用于使用资源。 运行时系统将保证 release effect 的执行,哪怕存在错误或中断。 和 ensuring 一样,brackets 具有语意成分,所以如果一个 bracket 嵌套在另一个 bracket 里面,那么当外部 bracket 获取资源的时候,也就意味着这个外部 bracket’s 的 release 函数也将会被执行,无论它的内嵌 bracket 的释放失败与否。 Next Steps 如果您对资源处理感到满意,那么下一步就是学习基本并发处理。

错误处理

本章节探讨一些常用的检测和处理错误的方法。 Either 您可以使用  ZIO#either 来处理故障,方法是使用  ZIO[R, E, A]  并产生  ZIO[R, Nothing, Either[E, A]]。 您可以使用  ZIO.absolve 来将(Either 类型的)失败反转成 ZIO,这与上例相反,它将  ZIO[R, Nothing, Either[E, A]] 转换为 ZIO[R, E, A]: 捕获所有错误 如果您想捕获所有类型的错误并有效地尝试恢复,则可以使用 catchAll 方法: 在传递给 catchAll的回调中,您可以返回具有不同错误类型(或可能为Nothing)的 effect,并且它将被反映在 catchAll 返回的 effect 类型中。 捕获特定的错误 如果您只想捕获并恢复某些类型的异常,则可以使用 catchSome 方法: 于 catchAll 不同, catchSome 不能减少或消除所能包含的错误类型,但是它可以将错误类型扩展为更广泛的错误类别。 失败回退 您可以使用 orElse 组合器让它尝试一种 effect,如果它失败了,那么继续尝试执行另一个 effect。 Folding Scala 的 Option 和 Either 数据类型都具有 fold 方法,它可以让您同时处理成功和失败。ZIO  effect 也具有类似的几种方式可以让您处理失败和成功。 第一种方法是使用 fold,通过为每种情况提供一个非效果的函数,让您以 “非效果” 的方式同时处理成功和失败: 第二种方式是使用 foldM 函数,它允许您通过为每种情况提供效果化的(纯净的)处理程序,来处理失败和成功: 几乎所有错误处理方法都是根据 foldM 定义的,因为它既强大又快速。 在下面的例子中,foldM 被同时用于处理 readUrls 方法的成功和失败的: 重试 ZIO数据类型上有许多有用的方法可以重试失败的 effect。 ZIO#retry 是其中最基本的用法,它接收一个 Schedule 并返回一个会在前次执行失败后,基于指定的策略尝试重新执行的 effect: 另一个强大的函数是 ZIO#retryOrElse,它允许指定一个失败回退函数,如果效果指定的策略也无法取得成功,则执行回退函数: 最后,ZIO#retryOrElseEither 函数允许返回失败回退函数返回一个不同的类型: 有关如何新建一个 schedules,请参考 Schedule. Next Steps 如果您对基本的错误处理感到满意,那么下一步就是学习安全的 资源管理。

BACK TO TOP