Map it to a result and pass the resulting promise to async(). Eg:
Well, how do you want to handle the exceptions? Do you want to ignore them? Do you want to send an error if just one of them fails? You gotta decide that first.
So first things first, handle the exceptions. You have two ways of doing that:
1) Don't throw exceptions. Wrap your computational expensive code in a try catch so it doesn't throw any exceptions, and always returns a result.
2) Use Promise.recover to map any exceptions that were encountered to a valid result.
Now if you just want to ignore the exceptions, then you're now done, because when an exception is thrown you just have to return some sort of default result (an empty list, null, whatever). However, if you want to do something if one of the computations failed, then you need to include the failure in the result, and then when you do your map function above, check each result to see if it was a failure. If there was a failure, return an error, otherwise return the results.
Typically in functional programming, we use a type called Either to represent this result. Either basically represents a single value that can either be one type, or another, but not both. We call one type the left type, and the other type the right type, and by convention, if one of the types represents an error, it's the left type.
So let's say we want to represent an error using a String (you could use more complex types if you want), then our initial promises might look like this:
Promise<Either<String, Integer>> promise1 = Akka.future(new Callable<Either<String, Integer>>() {
public Either<String, Integer> call() {
try {
return Either.Right(intensiveComputation1());
} catch (Exception e) {
return Either.Left(e.getMessage());
}
}
});
So in our map function we'd do something like this:
combinedPromises.map(new Function<List<Either<String, Integer>>, Result> {
public Result apply(List<Either<String, Integer>> results) {
List<Integer> successResults = new ArrayList<Integer>();
for (Either<String, Integer> result: results) {
if (result.left.isDefined()) {
return internalServerError(result.left.get());
}
successResults.add(result.right.get());
}
return Ok(Json.toJson(successResults));
}
}
And there you have it. At this point, Java's verbosity makes things really not that nice. The above code could be replaced with this Scala code:
combinedPromises.map { results =>
results.collectFirst {
case Left(msg) => InternalServerError(msg)
}.getOrElse(Ok(Json.toJson(results.map(_.right.get))))
}
Which makes it much easier to see the actual logic of your application, instead of having to ignore so much Java boiler plate. If you're going to be doing a lot of this stuff, I'd recommend using Scala.