如何使用包装 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.apply
将 Future[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))))
如果我没理解错的话,你Either
的Left
这边代表的是错误状态,对吗?
如果是这样,我认为您应该重构 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))
}
}
...
}
在我当前的项目中,我使用 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.apply
将 Future[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))))
如果我没理解错的话,你Either
的Left
这边代表的是错误状态,对吗?
如果是这样,我认为您应该重构 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))
}
}
...
}