Scala异步编程:WikiResult伴生对象traverse方法实现疑问(Failure处理相关)
Scala异步编程:WikiResult伴生对象traverse方法实现疑问(Failure处理相关)
我太懂你这种刚从Java转Scala,碰到异步+自定义Monad组合时脑子打结的感觉了!毕竟Scala的Future、Either再加上WikiResult这种封装,确实需要点时间磨明白。先看看你当前代码里的问题,再一步步给你捋清楚正确的实现思路。
首先你现在的foldLeft逻辑有个核心问题:直接去match b.value是完全错误的——Future的value属性是个Option[Try[...]],只有当Future已经完成时才有值,要是Future还在异步执行中,你拿到的就是None,这时候直接返回空序列等于完全丢掉了异步任务的结果。而且不管成功失败都返回空序列,根本没做实际的结果合并,这肯定不是你要的效果。
那正确的traverse应该怎么做呢?核心目标是把一个Seq[A]通过f转换成Seq[WikiResult[B]],然后把这些分散的WikiResult合并成一个WikiResult[Seq[B]],还要正确收集所有错误或者返回成功的结果列表。这里给你一个完整的实现:
import scala.concurrent.{ExecutionContext, Future} case class WikiError(message: String) // 假设你有这个错误定义 case class WikiResult[A](value: Future[Either[Seq[WikiError], A]]) object WikiResult: def successful[A](a: A): WikiResult[A] = WikiResult(Future.successful(Right[Seq[WikiError], A](a))) def traverse[A, B](as: Seq[A])(f: A => WikiResult[B])(using ExecutionContext): WikiResult[Seq[B]] = // 第一步:把每个A转换成对应的WikiResult[B],提取里面的Future[Either[...]] val individualFutures: Seq[Future[Either[Seq[WikiError], B]]] = as.map(a => f(a).value) // 第二步:用Future.sequence把多个Future合并成一个Future,里面装着所有Either的序列 val combinedFuture: Future[Seq[Either[Seq[WikiError], B]]] = Future.sequence(individualFutures) // 第三步:处理合并后的Either序列——收集所有错误,或者整理成功结果 val resultFuture: Future[Either[Seq[WikiError], Seq[B]]] = combinedFuture.map { eitherSeq => // 把所有错误和成功结果分开,用foldLeft手动处理(兼容旧Scala版本) val (allErrors, allSuccesses) = eitherSeq.foldLeft((Seq.empty[WikiError], Seq.empty[B])) { case ((errors, successes), either) => either match { case Left(errs) => (errors ++ errs, successes) case Right(b) => (errors, successes :+ b) } } // 如果有错误,返回合并后的错误序列;否则返回成功结果列表 if allErrors.nonEmpty then Left(allErrors) else Right(allSuccesses) } // 最后把处理好的Future包装成WikiResult返回 WikiResult(resultFuture)
给你拆解一下关键步骤:
- 提取单个Future:通过
as.map(a => f(a).value)把每个A对应的WikiResult里的Future取出来,得到一个Seq[Future[Either[...]]]。 - 合并Future:
Future.sequence是处理多个异步任务的神器,它能把一堆Future转换成一个Future,里面装着所有任务完成后的结果序列,这样我们就能在所有任务都完成后统一处理。 - 处理结果序列:用
map处理合并后的Future,把里面的Seq[Either]拆分成错误和成功结果两部分。如果有任何错误,就把所有错误合并成一个大的错误序列返回;如果全成功,就把所有成功结果整理成列表返回。 - 包装成WikiResult:最后把处理好的Future装进WikiResult里就搞定了。
另外还要提一句:如果你的需求是遇到第一个错误就终止(而不是收集所有错误),那实现逻辑会不一样,可以用foldLeft结合flatMap来逐个处理元素,一旦碰到错误就直接返回错误的WikiResult,不过通常traverse的语义是收集所有错误,所以上面的实现更贴合通用场景。
备注:内容来源于stack exchange,提问作者Christian H. Kuhn
相关产品推荐
相关产品推荐

