如何使用包装 Either 的 Future?

How to work with a an Future which wrappes an Either?

在我当前的项目中,我使用 Either[Result, HandbookModule]Result 是一个 HTTP 状态码)作为 return 类型,这样当出现问题时我可以创建正确的状态。我现在已经将我的数据库访问重构为非阻塞的。

此更改要求我的 return 数据库访问函数类型更改为 Future[Either[Result, HandbookModule]]

现在我不确定如何将此函数与 returns Either[Result, Long].

的另一个函数粘合在一起

所以为了更好地说明我的意思:

def moduleDao.getHandbooks(offset, limit): Future[Either[Result, List[Module]] = Future(Right(List(Module(1))))

def nextOffset(offset, limit, results): Either[_, Long] = Right(1)

def getHandbooks(
  offset: Long,
  limit: Long): Future[Either[Result, (List[HandbookModule], Long)]] = {
  for {
    results <- moduleDao.getHandbooks(offset, limit)
    offset  <- nextOffset(offset, limit, results)
  } yield (results, offset)
}

在更改之前,这显然没有问题,但我不知道最好的方法是什么。

或者有没有办法将 Future[Either[A, B]] 转换为 Either[A, Future[B]]

为了从 Future 中解包你的方法,你必须阻止它并等待结果。你可以使用 Await.result.

但是阻止未来通常不被认为是最佳实践。关于此的更多信息 here

所以你应该以不同的方式解决这个问题。您面临的实际上是嵌套 monad 堆栈的常见问题,可以使用 monad 转换器处理。

Scala 的函数式编程库cats provides an implementation of EitherT monad 转换器。

在您的情况下,您可以使用 EitherT.applyFuture[Either[Result, List[Module]] 转换为 EitherT[Future, Result, List[Module]] 并使用 EitherT.fromEither 提升 Either[_, Long]

它可能看起来像这样:

import cats.data.EitherT
import cats.implicits._
  
def getHandbooks(
   offset: Long,
   limit: Long
): Future[Either[String, (List[String], Long)]] = {
  val result: EitherT[Future, String, (List[String], Long)] = for {
    results <- EitherT(moduleDao.getHandbooks(offset, limit))
    offset  <- EitherT.fromEither[Future](nextOffset(offset, limit, results))
  } yield (results, offset)

  result.value //unwrap result from EitherT
}

我必须对发布的代码做出一些假设和 adjustments/corrections 才能使其可用。 (你不会让那些想帮助你的人好过。)

如果您可以容忍默认的 Long 值,当 nextOffset() returns Left 而不是 Right[Long] 时,这似乎是类型检查和编译.

def getHandbooks(offset: Long
                ,limit : Long
                ): Future[Either[Result, (List[Module], Long)]] =
  moduleDao.getHandbooks(offset,limit).map(_.map(ms => 
    (ms, nextOffset(offset,limit,ms).getOrElse(0L))))

如果我没理解错的话,你EitherLeft这边代表的是错误状态,对吗?

如果是这样,我认为您应该重构 API 以不使用 Either,而只需使用失败的 Future 来表示错误状态。大致如下:

// Custom exception that wraps existing Result
case class MyCustomException(result: Result) extends Exception

class ModuleDao {
  ...
  def getHandbooks(offset, limit): Future[List[Module] = {
    // You'd probably want to do this asynchronously
    // But for demonstration purposes
    val origRetVal: Either[Result, List[Module] = ??? // current code returning your Either
    origRetVal match {
      case Right(modules: List[Module]) =>
        Future.successful(modules)
      case Left(result: Result) =>
        // Failed future wrapping custom exception
        Future.failed(MyCustomException(result))
    }
  }
  ...
}