我们使用Twitter期货(作为Finagle堆栈的一部分),我不喜欢使用(业务)异常来控制应用程序流的概念,因为异常不会出现在方法签名中。
所以我有了使用Future [Aither [A,B]]作为替代品的想法。
但是我在使用这个概念来理解期货时遇到了一些问题:
E.g。我们有一个存储库方法:
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的详细错误信息。
答案 0 :(得分:4)
所以现在我已经尝试使用Scalaz Either(这是一个正确的偏向Either与中性scala 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))
}
答案 1 :(得分:2)
您可以通过隐式添加处理Either
的方法来扩展Future类,而不是每次都必须自己匹配它:
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
。