You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Scala/Play框架中处理多REST调用并合并资源信息?

Awesome question! When you need to fire off multiple REST calls in Scala/Play, wait for all of them to complete, then merge the results, you’ve got some perfect tools built right into the ecosystem. Let’s walk through the best approaches with practical examples.

Best Approaches for Parallel REST Calls & Result Merging in Scala/Play

1. Future.sequence + Play WSClient (Most Common for Same-Type Resources)

This is the go-to pattern when you’re fetching multiple instances of the same resource type. Play’s WSClient returns Future[WSResponse] for every REST call, and Future.sequence turns a list of these futures into a single Future[List[WSResponse]]—so you can wait for all calls to finish before merging.

Here’s a concrete example:

import play.api.libs.ws._
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.json._

// Example data models
case class Resource(id: String, details: String)
object Resource {
  implicit val format: OFormat[Resource] = Json.format[Resource]
  val empty: Resource = Resource("", "Failed to fetch")
}
case class MergedResult(totalResources: Int, combinedDetails: String)

class ResourceService @Inject()(ws: WSClient)(implicit ec: ExecutionContext) {
  // Fetch a single resource via REST
  private def fetchSingleResource(id: String): Future[Resource] = {
    ws.url(s"https://your-api.com/resources/$id")
      .get()
      .map(_.json.as[Resource])
      .recover { case _ => Resource.empty } // Gracefully handle individual call failures
  }

  // Fetch all resources and merge results
  def fetchAndMerge(ids: List[String]): Future[MergedResult] = {
    // Create a list of futures for each resource call
    val resourceFutures: List[Future[Resource]] = ids.map(fetchSingleResource)
    
    // Wait for all futures to resolve, then merge
    Future.sequence(resourceFutures)
      .map(resources => {
        MergedResult(
          totalResources = resources.size,
          combinedDetails = resources.map(_.details).mkString(" | ")
        )
      })
  }
}

2. Future.zip / Future.traverse (For Mixed-Types or Custom Per-Request Logic)

If you’re fetching different types of resources (e.g., a user and their orders), use Future.zip (for 2 requests) or Future.zip3/zipN (for more). For scenarios where you need to transform each request’s input before fetching, Future.traverse (a combination of map + sequence) works perfectly.

Example with Future.zip for mixed types:

case class User(id: String, name: String)
case class Order(id: String, total: Double)

// Fetch a user and their latest order in parallel
def fetchUserAndOrder(userId: String, orderId: String): Future[(User, Order)] = {
  val userFuture = ws.url(s"https://your-api.com/users/$userId")
    .get().map(_.json.as[User])
  val orderFuture = ws.url(s"https://your-api.com/orders/$orderId")
    .get().map(_.json.as[Order])

  Future.zip(userFuture, orderFuture)
}

3. Handling Partial Failures with Future.fold

If you want to continue processing even if some REST calls fail (instead of failing the entire batch), Future.fold lets you accumulate results incrementally, skipping or handling failures as you go.

Example:

def fetchAllWithPartialFailureHandling(ids: List[String]): Future[List[Resource]] = {
  // Start with an empty list, then add each successful resource to it
  Future.fold(ids.map(fetchSingleResource))(List.empty[Resource]) { (accumulator, resource) =>
    if (resource != Resource.empty) accumulator :+ resource else accumulator
  }
}

Bonus: Integrate with Play’s Async Controllers

If you’re building a Play endpoint, return a Future[Result] to keep your app non-blocking:

import play.api.mvc._

class ResourceController @Inject()(service: ResourceService, cc: ControllerComponents)(implicit ec: ExecutionContext) 
  extends AbstractController(cc) {

  def getMergedResources(ids: String) = Action.async {
    val idList = ids.split(",").toList
    service.fetchAndMerge(idList)
      .map(mergedResult => Ok(Json.toJson(mergedResult)))
      .recover { case e => InternalServerError(s"Merge failed: ${e.getMessage}") }
  }
}

Quick Recap

  • Use Future.sequence for same-type resources in bulk
  • Use Future.zip/zipN for fixed numbers of different-type resources
  • Use Future.fold + recover when you need to handle partial failures
  • Always use Play’s injected ExecutionContext to avoid thread pool issues

内容的提问来源于stack exchange,提问作者user9584853

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:02:15