scala, play, futures: combining results from multiple futures

David Kaczynski picture David Kaczynski · Feb 24, 2013 · Viewed 8.7k times · Source

I am using:

  • Scala 2.10
  • Play 2.1

Currently, I am using the Future class from scala.concurrent._, but I'm open to trying another API.

I am having trouble combining the results of multiple futures into a single List[(String, String)].

The following Controller method successfully returns the results of a single Future to an HTML template:

  def test = Action { implicit request =>
    queryForm.bindFromRequest.fold(
      formWithErrors => Ok("Error!"),
      query => {
        Async { 
          getSearchResponse(query, 0).map { response =>
            Ok(views.html.form(queryForm,
              getAuthors(response.body, List[(String, String)]())))
          }
        }
      })
  }

The method getSearchResult(String, Int) performs a web service API call and returns a Future[play.api.libs.ws.Response]. The method getAuthors(String, List[(String, String)]) returns a List[(String, String)] to the HTML template.

Now, I am trying to call getSearchResult(String, Int) in a for loop to get several Response bodies. The following should give an idea of what I'm trying to do, but I get a compile-time error:

  def test = Action { implicit request =>
    queryForm.bindFromRequest.fold(
      formWithErrors => Ok("Error!"),
      query => {
        Async {
          val authors = for (i <- 0 to 100; if i % 10 == 0) yield {
            getSearchResponse(query, i)
          }.map { response =>
            getAuthors(response.body, List[(String, String)]())
          }

          Ok(views.html.form(queryForm, authors))
        }
      })
  }

type mismatch; found : scala.collection.immutable.IndexedSeq[scala.concurrent.Future[List[(String, String)]]] required: List[(String, String)]

How can I map the responses of several Future objects to a single Result?

Answer

djechlin picture djechlin · Feb 24, 2013

Create a Future parametrized by a List or other Collection of the Result type.

From here:

In Play 1 you can do this:

    F.Promise<List<WS.HttpResponse>> promises = F.Promise.waitAll(remoteCall1, remoteCall2, remoteCall3);

    // where remoteCall1..3 are promises

    List<WS.HttpResponse> httpResponses = await(promises); // request gets suspended here

In Play 2 less direct:

    val httpResponses = for {
  result1 <- remoteCall1
  result2 <-  remoteCall2
} yield List(result1, result2)