如果您使用 Cats,您可以使用称为 monad 转换器的东西,特别是 EitherT 用于您的情况:
import cats.implicits._
import cats.data.EitherT
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
type User
type UserStatus
type Transaction
type TransactionStatus
def processUser(user: User): Future[Either[Error, UserStatus]] = ???
def processTransaction(tx: Transaction): Future[Either[Error, TransactionStatus]] = ???
def unprocessUser(user: User): Future[Either[Error, TransactionStatus]] = ???
val user : User = ???
val tx : Transaction = ???
def resCatsFuture(): Future[Either[Error, Unit]] =
(for {
userStatus <- EitherT(processUser(user))
txStatus <- EitherT(processTransaction(tx))
.handleErrorWith(e => EitherT(unprocessUser(user)))
} yield ()).value
由于我们在 Cats 上,您还应该考虑将 Future 替换为 Cats Effect 中的 IO:
import cats.effect._
def processUserIO(user: User): IO[Either[Error, UserStatus]] = ???
def processTransactionIO(tx: Transaction): IO[Either[Error, TransactionStatus]] = ???
def unprocessUserIO(user: User): IO[Either[Error, TransactionStatus]] = ???
val resCatsIO: IO[Either[Error, Unit]] =
(for {
userStatus <- EitherT(processUserIO(user))
txStatus <- EitherT(processTransactionIO(tx))
.handleErrorWith(e => EitherT(unprocessUserIO(user)))
} yield ()).value
或者使用ZIO 的输入错误作为 monad 转换器的替代方案:
import zio._
def processUserZIO(user: User): ZIO[Any, Error, UserStatus] = ???
def processTransactionZIO(tx: Transaction): ZIO[Any, Error, TransactionStatus] = ???
def unprocessUserZIO(user: User): ZIO[Any, Error, TransactionStatus] = ???
val resZIO: ZIO[Any, Error, Unit] =
for {
userStatus <- processUserZIO(user)
txStatus <- processTransactionZIO(tx)
.catchAll(e => unprocessUserZIO(user))
} yield ()
如果您想坚持使用原版 Scala,您可以在内部 Either 上使用 fold,并为 Left 和 Right 两种情况返回一个新的 Future,这样您就可以继续理解。
def unprocessUser2(user: User): Future[Transaction] = ???
def unprocessUser3(user: User): Future[Unit] = ???
val res: Future[Unit] =
for {
mUserStatus <- processUser(user)
userStatus <- mUserStatus.fold(Future.failed, Future.successful) //you could also do `Future.fromTry(mUserStatus.toTry)` here
mTxStatus <- processTransaction(tx)
txStatus <- mTxStatus.fold(e => unprocessUser2(user), Future.successful)
//or alternatively, depending on how you want to handle the `Left` and Right values
_ <- mTxStatus.fold(e => unprocessUser3(user), tx => Future(println(tx)))
} yield ()
//Redundant, but for consistency, if you want to keep to the same `Either` inner type.
val resEither: Future[Either[Error, Unit]] =
for {
mUserStatus <- processUser(user)
userStatus <- mUserStatus.fold(Future.failed, Future.successful)
mTxStatus <- processTransaction(tx)
mTxStatus2 <- mTxStatus.fold(e => unprocessUser(user), tx => Future.successful(Right(tx)))
} yield Right()
我对@987654335@ 应该如何工作以及应该在哪里处理做出了很多假设。 Cats 和 ZIO 中还有其他错误处理方法可能更适合您的用例。