4

我们使用 Twitter 期货(作为 Finagle 堆栈的一部分),我不喜欢使用(业务)异常来控制应用程序流的概念,因为异常不会出现在方法签名中。

所以我有了用 Future[Either[A,B]] 作为替代的想法。

但是我在使用这个概念对期货进行理解时遇到了一些问题:

例如,我们有一个存储库方法:

def getUserCredentialsByNickname(nickname: String): Future[Either[EntityNotFound, UserCredentials]]

和一个处理程序方法,它使用这个 repo 并执行一些其他检查并创建一个令牌

def process(request: LoginRequest): Future[Either[Failure, Login]] = {
      for {
        credentialsEither <- userRepository.getUserCredentialsByNickname(request.username)
        ...several other calls/checks which should 'interrupt' this for comprehension
        token <- determineToken(credentials)
} yield token

getUserCredentialsByNickname(..) 之后的 for comprehension 中的调用仅应在此调用返回 Right[UserCredentials] 时执行,而且每个返回的 Either 的详细错误信息也应从处理程序返回。

4

2 回答 2

4

所以现在我尝试使用 Scalaz Either(与中性 scala Either 相比,这是一个偏右的 Either)和 Monad Transformer EitherT,它似乎正是我想要的。感谢 Huw,尤其是 Lars Hupel,他们向我暗示了正确的方向。

这是 Twitter 期货和 Scalaz Either 和 EitherT 的示例:

import com.twitter.util.{Await, Future}
import scalaz.{Monad, Functor, EitherT, \/}
import scalaz.syntax.ToIdOps

object EitherTest extends App with ToIdOps{

  // make Twitter futures work with EitherT
  implicit val FutureFunctor = new Functor[Future] {
    def map[A, B](a: Future[A])(f: A => B): Future[B] = a map f
  }
  implicit val FutureMonad = new Monad[Future] {
    def point[A](a: => A): Future[A] = Future(a)
    def bind[A, B](fa: Future[A])(f: (A) => Future[B]): Future[B] = fa flatMap f
  }

  // The example begins here:

  case class InvalidInfo(error: String)
  case class Response(msg: String)


  class ComponentA {
    def foo(fail: Boolean): Future[\/[InvalidInfo, Response]] = {
      if(fail) Future(InvalidInfo("Error A").left) else Future(Response("ComponentA Success").right)
    }
  }
  class ComponentB {
    def bar(fail: Boolean): Future[\/[InvalidInfo, Response]] = {
      if(fail) Future(InvalidInfo("Error B").left) else Future(Response("ComponentB Success").right)
    }
  }

  val a = new ComponentA
  val b = new ComponentB

  val result = for {
    resultA <- EitherT(a.foo(false))
    resultB <- EitherT(b.bar(false))
  } yield (resultA, resultB)

  println(Await.result(result.run))
}
于 2013-09-23T09:25:08.787 回答
2

您可以通过隐式添加一个处理方法来扩展 Future 类Either,而不必每次都自己匹配它:

implicit class EitherHandlingFuture[Exception, Value](future: Future[Either[Exception, Value]]) {
  def mp[Return](fn: Value => Return) = {
    future.map { eth: Either[Exception, Value] =>
      eth match {
        case Left(ex: Exception) => { print("logging the exception") /* handle or rethrow */ }
        case Right(res: Value) => fn(res)
      }
    }
  }
}

那么,这将是可能的:

def someComputation: Future[Either[Exception, Int]] = Future.value(Right(3))

someComputation mp { res: Int =>
  println(res)
}

请注意,上面的代码片段不for支持理解,因为要支持它们,必须完全实现 map/flatMap。为此,您可能想要子类化Future.

于 2013-09-16T14:50:33.650 回答