Suppose I have several futures and need to wait until either any of them fails or all of them succeed.
For example: Let there are 3 futures: f1
, f2
, f3
.
If
f1
succeeds andf2
fails I do not wait forf3
(and return failure to the client).If
f2
fails whilef1
andf3
are still running I do not wait for them (and return failure)If
f1
succeeds and thenf2
succeeds I continue waiting forf3
.
How would you implement it?
You can use a promise, and send to it either the first failure, or the final completed aggregated success:
Then you can
Await
on that resultingFuture
if you want to block, or justmap
it into something else.The difference with for comprehension is that here you get the error of the first to fail, whereas with for comprehension you get the first error in traversal order of the input collection (even if another one failed first). For example:
And:
You can use this:
For this purpose I would use an Akka actor. Unlike the for-comprehension, it fails as soon as any of the futures fail, so it's a bit more efficient in that sense.
Then, create the actor, send a message to it (so that it will know where to send its reply to) and wait for a reply.
You can do this with futures alone. Here's one implementation. Note that it won't terminate execution early! In that case you need to do something more sophisticated (and probably implement the interruption yourself). But if you just don't want to keep waiting for something that isn't going to work, the key is to keep waiting for the first thing to finish, and stop when either nothing is left or you hit an exception:
Here's an example of it in action when everything works okay:
But when something goes wrong:
You might want to checkout Twitter's Future API. Notably the Future.collect method. It does exactly what you want: https://twitter.github.io/scala_school/finagle.html
The source code Future.scala is available here: https://github.com/twitter/util/blob/master/util-core/src/main/scala/com/twitter/util/Future.scala
You could use a for-comprehension as follows instead:
In this example, futures 1, 2 and 3 are kicked off in parallel. Then, in the for comprehension, we wait until the results 1 and then 2 and then 3 are available. If either 1 or 2 fails, we will not wait for 3 anymore. If all 3 succeed, then the
aggFut
val will hold a tuple with 3 slots, corresponding to the results of the 3 futures.Now if you need the behavior where you want to stop waiting if say fut2 fails first, things get a little trickier. In the above example, you would have to wait for fut1 to complete before realizing fut2 failed. To solve that, you could try something like this:
Now this works correctly, but the issue comes from knowing which
Future
to remove from theMap
when one has been successfully completed. As long as you have some way to properly correlate a result with the Future that spawned that result, then something like this works. It just recursively keeps removing completed Futures from the Map and then callingFuture.firstCompletedOf
on the remainingFutures
until there are none left, collecting the results along the way. It's not pretty, but if you really need the behavior you are talking about, then this, or something similar could work.