0

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!

Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
joesan
  • 13,963
  • 27
  • 95
  • 232

3 Answers3

2

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.

Community
  • 1
  • 1
Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
  • Async { Ok(jsonResponse) } - This won't compile as I have to convert this Future[String] to a Result! – joesan Feb 12 '14 at 12:59
  • Ahh right, it needs to be a `Future[Response]`, not a response containing a future. Will update the answer. – Kevin Wright Feb 12 '14 at 13:02
  • Another question: Is the ftrList would contain all the responses for all the elements in the strList? Should I consider adding an onComplete handler? Any suggestions? – joesan Feb 12 '14 at 13:12
  • `ftrList` is a single `Future` that will complete with a `List` when all the nested futures have completed. You don't need to add your own `onComplete` handler as Play Async already handles processing of this future when it completes. – Kevin Wright Feb 12 '14 at 13:16
  • This works fine as long as there are no errors. How do I now handle error scenarios? For example., for any one of the elements in the strList, the longRunningCall might throw and Exception. When that happens, I would like to return the exception message back or even more better, I would like to collect all the errors and send the error string messages back. – joesan Feb 12 '14 at 14:11
  • @ViktorKlang No good reason, guess I just wanted to demonstrate how you map over Futures – Kevin Wright Feb 12 '14 at 15:59
  • @user3102968 I added a way to handle failures – Kevin Wright Feb 14 '14 at 14:56
0

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

S.Karthik
  • 1,389
  • 9
  • 21
  • It could be that the long running call might throw an exception. What do I do in that case? Should I case match? – joesan Feb 12 '14 at 09:48
  • Akka.future {longRunningCall(s)}.recover{ case e:Exception =>{e.getLocalizedMessage()} } this will return the error message instead result string – S.Karthik Feb 12 '14 at 09:58
0

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

adamretter
  • 3,885
  • 2
  • 23
  • 43