I have the following test code snippet:
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.util.Success
import scala.concurrent.ExecutionContext.Implicits.global
object FutureAndThen extends App {
val future = Future {
println("Started initial Future")
10
} andThen { case Success(value) =>
println("Started callback")
Thread.sleep(5000)
println(s"Finished callback: value = $value")
} map { x =>
println("Chained transformation")
x * 2
}
println(Await.result(future, Duration.Inf))
}
It produces the following output:
Started initial Future
Started callback
Finished callback: value = 10
Chained transformation
20
I expect andThen
callback to be executed asynchronously. But the actual execution is the next:
map
)At first I thought that the issue is in ExecutionContext
which decided to run all these operations in single thread. And I changed this to use custom ExecutionContext
:
implicit val ctx = ExecutionContext.fromExecutor(
(command: Runnable) => new Thread(command).start()
)
And the result is the same. Could you advice me what I am missing?
This behavior is actually documented for Future.andThen
:
Applies the side-effecting function to the result of this future, and returns a new future with the result of this future.
This method allows one to enforce that the callbacks are executed in a specified order.
It means that map
will not start its job before the calculations inside andThen
are completed. If this is not what you want, you have to call map
on the original Future
. And then you can use onComplete
instead of andThen
, so code would become something like this:
val future = Future {
println("Started initial Future")
10
}
future onComplete { case Success(value) =>
println("Started callback")
Thread.sleep(2000)
println(s"Finished callback: value = $value")
}
val f2 = future map { x =>
println("Chained transformation")
x * 2
}
println(Await.result(f2, Duration.Inf))
P.S. AFAIK there is no standard onComplete
equivalent that can be used with method chaining and I think this is by design to make it easier to predict behavior by reading the code. Currently you can use a simple rule: if it is chained - it is executed later.