这取决于您的数据是什么样的以及您希望如何处理失败的期货,但如果您不想处理来自未来的特定类型的 Throwable,这里有一些选项:
// EitherT[Future, E, A] to IO[E, A]
val io: IO[E, A] =
ZIO.fromFuture(eitherT.value)
.orDie
.flatMap(ZIO.fromEither)
// EitherT[Future, Throwable, A] to Task[A]
val task: Task[A] =
ZIO
.fromFuture(_ => et.value)
.flatMap(ZIO.fromEither)
// EitherT[Future, Throwable, A] to UIO[A]
val task: UIO[A] =
ZIO
.fromFuture(_ => et.value)
.flatMap(ZIO.fromEither)
.orDie
更长的解释
首先,没有用于从EitherT 转换的内置函数,因此您需要使用底层的Future,您可以从中快速获取ZIO Task:
val eitherT: EitherT[Future, A, B] = ???
val underlying: Future[Either[A, B]] = eitherT.value
// Note that this is equivalent to IO[Throwable, Either[...]]
val zioTask: Task[Either[MyError, MyResult]] = ZIO.fromFuture(_ => underlying)
// Or all at once:
val zioTask = ZIO.fromFuture(_ => eitherT.value)
我和 OP 有类似的需求,但就我而言,左派也不例外,而是我自己的 MyError:
val underlying: Future[Either[MyError, MyResult]]
如果您也遇到类似情况,那么您需要决定如何处理可能在未来失败的任何异常。一种可能的选择是...
忽略它们,如果它们发生,让纤维死亡
如果将来的异常代表代码中的缺陷,那么通常没有其他选择,只能让光纤死掉,因为它无法继续进行。用orDie 杀死的光纤将有一个Throwable 代表原因,但您可以选择(我建议)使用您自己的异常,详细说明通过orDieWith 发生故障的方式和原因。
val uio: UIO[Either[MyError, MyResult]] = task.orDie
// Or
case class ExplanatoryException(cause: Throwable)
extends RuntimeException(
"A failure was encountered when we used XYZ-lib/made service call to FooBar/etc",
cause
)
val uio: UIO[Either[MyError, MyResult]] = task.orDieWith(ExplanatoryException)
// Then finally
val fullyConverted: IO[MyError, MyResult] = uio.flatMap(ZIO.fromEither)
现在您可以使用干净的IO!
扩展方法!
如果您经常这样做,您可以向EitherT 添加一个扩展方法来为您进行转换:
implicit class EitherTOps[A, B](et: EitherT[Future, A, B]) {
def toIO: IO[A, B] = ZIO.fromFuture(_ => et.value)
.orDie
.flatMap(ZIO.fromEither)
def toIOWith(f: Throwable => Throwable): IO[A, B] = ZIO.fromFuture(_ => et.value)
.orDieWith(f)
.flatMap(ZIO.fromEither)
}
还有更多!
如果您能够约束A <: Throwable,那么为EitherT 编写一些方便的toTask 扩展方法就很容易了,但我将把它作为练习留给读者。
如果你先验知道你可能从 Future 得到什么样的Throwable,并且你想真正优雅地处理它,那么请参阅this question,它涵盖了所有的方法。