【问题标题】:How to work with a an Future which wrappes an Either?如何使用包装了 Either 的 Future?
【发布时间】:2021-03-09 19:10:41
【问题描述】:

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

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

现在我不确定如何将此函数与另一个返回 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]]

【问题讨论】:

  • 为什么需要改变它?为什么不只是在它有效的情况下计算未来?
  • 在我提供的示例中,这是可行的,但我也有一些更复杂的情况。
  • 尝试为您的复杂案例创建一个minimal reproducible example,这样会更容易帮助您。
  • 好的,我添加了一个更复杂的示例。希望有用
  • 您的示例无法编译。

标签: scala functional-programming future either


【解决方案1】:

为了从 Future 中解开您的方法,您必须阻止它并等待结果。你可以使用Await.result来做到这一点。

但阻止未来通常不被认为是最佳做法。更多关于这个here

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

Scala 的函数式编程库cats 提供了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
}

【讨论】:

    【解决方案2】:

    我必须对发布的代码进行一些假设和调整/更正才能使其可用。 (对于那些想要帮助你的人来说,你并不容易。)

    如果您可以容忍默认的Long 值,当nextOffset() 返回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))))
    

    【讨论】:

      【解决方案3】:

      如果我理解正确的话,你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))
          }
        }
        ...
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-10-27
        • 1970-01-01
        • 2021-01-18
        • 2019-08-12
        • 2015-01-05
        • 2012-12-19
        • 2021-10-22
        • 2016-01-23
        相关资源
        最近更新 更多