Reputation: 15425
I have a scenario wherein I get a List of String messages and I have to iterate through the String and call another method which is sort of a long running process. I have to then collect the results of this long running process and concatenate the results and send it back to the user interface. I'm pretty new to these Future concepts in Scala. I'm using Play framework where-in the list of Strings will come from the user interface. Here is how my first attempt at implementing ht scenario looks:
def futuresTest(strList: List[String]) = Action {
Async {
val ftrList: List[Future[String]] =
strList.map(s => Akka.future {longRunningCall(s)} ).toList
val futureResultList = Future.sequence(ftrList)
val jsonResponse: String =
futureResultList.map(_.sum).asInstanceOf[String]
Akka.future { Ok(jsonResponse) }
}
}
For simplicity, the longRunningCall would just return a String. Later I will tie it up to the original implementation.
def longRunningCall(s: String) = "test"
My question here is that in the statement:
val ftrList: List[Future[String]] =
strList.map(s => Akka.future {longRunningCall(s)} ).toList
I would assume that the ftrList will be populated asynchronously and when it hits the following line, I'm guaranteed that the futureResultList would contain all the elements (i.e., strList and futureResultList size would be equal?
val futureResultList = Future.sequence(ftrList)
Please advice!
Upvotes: 0
Views: 188
Reputation: 49705
I'm going to assume here that you mean for the strings to be concatenated. First some comments on the code:
There's no need to wrap the whole block in an Async, just the final future that you're returning.
The value types can all be inferred, you don't need to state them explicitly
mapping a List
returns a List
. Calling toList
on the result is redundant.
List#sum
only works on numbers, use foldLeft
instead for strings.
A Future[String]
can not be cast directly to a String
via asInstanceOf
. You'll be returning it as a Future
anyway.
map
and Future.sequence
can be combined into a single Future.traverse
operation.
And changing the code to apply these points:
def futuresTest(strList: List[String]) = Action {
val ftrList = Future.traverse(strList) {
s => Future( longRunningCall(s) )
}
// this will be a Future[String]
val jsonResponse = ftrList map { _.foldLeft("")(_ + _) }
Async { jsonResponse map (OK(_)) }
}
The last two lines could also be combined:
Async {
ftrList map { xs => OK(xs.foldLeft("")(_ + _)) }
}
UPDATE
Here's the same thing using Future.fold, as suggested by Viktor
def futuresTest(strList: List[String]) = Action {
val ftrList = strList map { longRunningCall(_) } // List[Future]
val jsonResponse = Future.fold(ftrList)("")(_ + _) // Future[String]
Async { jsonResponse map (OK(_)) }
}
For handling failure, you want to recover the future and have it return a different response:
Async { jsonResponse map (OK(_)) recover (InternalServerError(_.toString)) }
If you want to handle individual errors from each element, then you should look at the technique used in this answer.
Upvotes: 2
Reputation: 3517
It would seem to me that for what you want it would be much simpler here to just use Scala's Parallel collections rather than Futures. By using .par.map
you can execute your long running operations on each item in a list in parallel and collect the results together.
def getResponse(strList: List[String]) = Action {
val results: List[String] = strList.par.map(longRunningCall(_))
val jsonResponse: String = results.mkString(",") //concatenate using ','
Ok(jsonResponse)
}
http://docs.scala-lang.org/overviews/parallel-collections/configuration.html
Upvotes: 0
Reputation: 1389
What you are doing will work correct, as long as the longRunningCall will correctly return values. ftrList size will equal to strList size, unless exception from longRunningCall
Upvotes: 0