问题
Future可以将with的情况推广到某种效果F[_],因此我们可以说:我们如何F[_]以某种方式构成效果,如果其中一个失败,F[_]则将通过某个函数回滚先前的执行结果?此行为类似于我们称为“交易”的行为。
F[_]
解决方案
对于以下cats-effect版本的代码解决方案2.3.1。最终的解决方案如下所示:
cats-effect
/** * Transactional effect provide possibility to recover effect execution result if it failed, but still return failed * result. * * @tparam F surrounding effect type */ class TransactionEffect[F[_], E](underlying: F[E], rollback: PartialFunction[Throwable, F[Unit]]) (implicit F: FlatMap[F], ME: MonadError[F, Throwable]) { /* * Here goes syntax trick - in for-comprehension will be invoked `flatMap` of this wrapper and not of underlying effect. */ def flatMap[S](f: E => F[S]): F[S] = { F.flatMap(underlying)(f).recoverWith { case exception: Throwable => val failure: F[S] = ME.raiseError[S](exception) rollback.lift(exception).fold(failure)(recoverEffect => F.flatMap(recoverEffect)(_ => failure)) } } } object TransactionEffect { /** * Provides syntax sugar over [[TransactionEffect]] wrapper. */ implicit class TransactionEffectSyntax[F[_], E](underling: F[E]) (implicit F: FlatMap[F], AE: MonadError[F, Throwable]){ def rollbackWith(rollback: PartialFunction[Throwable, F[Unit]]): TransactionEffect[F, E] = { new TransactionEffect[F, E](underling, rollback) } } }
和小演示:
object TransactionEffectDemo extends IOApp { import TransactionEffect._ override def run(args: List[String]): IO[ExitCode] = { for { _ <- IO.delay(println("A executed")).rollbackWith { case _: Throwable => IO.delay(println("A recovered")) } _ <- IO.delay(println("B executed")).rollbackWith { case _: Throwable => IO.delay(println("B recovered")) } _ <- IO.raiseError(new Exception("C failed")) } yield ExitCode.Success } }
它将打印出下一个输出(为便于说明,将其缩短):
A executed B executed B recovered A recovered java.lang.Exception: C failed
现实生活中的用法
如果应用程序是用纯函数式编程范式编写的,则理想情况下,它不应具有在其他效果失败的情况下需要“回滚”的任何副作用。但是,例如,当应用程序将文档存储在多个数据库(例如Mongo和Elasticsearch)中,并且如果在Elasticsearch中存储失败时,会从主MongoDB中删除文档时,此方法会很有用。
感谢您的关注,希望对您有所帮助!
原文链接:http://codingdict.com