1. Overview
In this tutorial, we are going to discuss how we can use the functions available on Futures to take a List of Futures and filter out any failures. There has been some improvement to the standard Scala library throughout Scala 2, so we’ll discuss different ways to tackle this problem depending on which version of Scala we’re using.
2. Using Future.sequence()
To start off, we’ll be using the Future.sequence() function. This is called on the Future object and takes a List[Future[T]] to convert it to a Future[List[T]], which is a lot easier to reason about within our code. Let’s say we have a list of Futures:
val futures = List(Future.successful("String 1"), Future.successful("String 2"))
We could then call Future.sequence() on our List:
Future.sequence(future)
This would then convert our List[Future[String]] to Future[List[String]]. Making the following steps in solving our problem a lot simpler.
3. Using recover()
If we’re using Scala 2.11 or earlier, the best way to separate successful and failed Futures, is to use recover wrapping to results in a Try. We can start by mapping over our successful Futures and wrapping to results in a Success Try:
future.map(Success(_))
After this step, we need to deal with the failed Futures. This can be done using the recover function available on Futures, to allow us to wrap our failed Futures in a Failure Try. We can combine these two steps:
future.map(Success(_)).recover { case x => Failure(x) }
4. Using transform()
From Scala 2.12 onwards, the transform() function on Futures was improved, so we can now use it to make our solution more concise:
future.transform(Success(_))
This produces the same result as the steps in the previous section but without the need to call .recover.
5. Processing Success/Failures with collect
So far, we have a Future[List[Try[String]]. Since we are only interested in the Futures that we’ve successful we need to filter out all the failed Futures. We can do this using the collect function on List:
future.collect { case Success(str) => str }
Calling .collect will reduce our List to only the Trys that match our partial function, which here is checking for a Success. Alternatively, if we only want the failed Futures, we can change the partial function to look for Failures:
future.collect { case Failure(ex) => ex }
6. Full Solution Using recover
Let’s put all these steps together to create a full solution. If we’re using Scala 2.11 or earlier, we can put all these steps together using recover:
Future.sequence(
futures.map(f => {
f.map(Success(_)).recover { case x => Failure(x) }
}
)
)
.map(f => f.collect { case Success(str) => str })
We start by making the call to Future.sequence(), to convert our List[Future[String]] to Future[List[String]]. Inside the call to sequence() we are mapping over every Future and using map() and recover() to convert the result of the Future to a Try.
Finally, we map over the resulting Future, then call collect() to filter down to only the successful Futures.
7. Full Solution Using transform()
If we’re using Scala 2.12 or later, we can put all these steps together using transform:
Future.sequence(
futures.map(f => f.transform(Success(_)))
)
.map(f => f.collect { case Success(str) => str })
In this snippet, we are doing the same as the code in the section above. However, the step to convert the result of the Future to a Try is replaced by the less verbose call to transform().
8. Conclusion
In the article, we have covered how we can use the functions available on Futures, to filter a List[Future[T]] to leave us with only the successful Futures. We started with Future.sequence to transform it into a Future[List[T]].
We’ve then learned about how we can use recover() in Scala 2.11 and below or transform() in Scala 2.12 and above to filter out successful and failed Futures. Throughout the article, we’ve dissected each step and discussed what each function is doing to help us achieve the desired result.
As always, the sample code used in this article is available over on GitHub.