17

In Scala, I can use Await to wait for a future to complete. However, if I have registered a callback to run upon completion of that future, how can I wait not only for the future to complete but also for that callback to finish?

Here is a minimal but complete program to illustrate the problem:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    f.onSuccess { case _ =>
      Thread.sleep(10000)
      println("The program waited patiently for this callback to finish.")
    }

    // This waits for `f` to complete but doesn't wait for the callback
    // to finish running.
    Await.ready(f, Duration.Inf)
  }
}

I expect the output to be:

The program waited patiently for this callback to finish.

Instead, there is no output; the program exits before the callback finishes.

Please note that this is not the same problem as waiting for a future to complete, which has been answered previously at this question.

Dan Li
  • 866
  • 1
  • 7
  • 19

2 Answers2

26

Don't use an onSuccess callback, but instead do the side effect in a Future.map call. That way, you have a Future[Unit] to use Await on.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    val f2: Future[Unit] = f.map { x =>
      Thread.sleep(10000)
      println("The program waited patiently for this callback to finish.")
    }

    Await.ready(f2, Duration.Inf)
  }
}

Note that if you want to execute a side effect only in case of success (like in your example), map is appropriate. If you want to execute a side effect also in case of failure, andThen is the right method to use. See this post from Roland Kuhn on scala-user.

Also, please don't use Thread.sleep anywhere near production code.

Rüdiger Klaehn
  • 12,445
  • 3
  • 41
  • 57
  • 1
    There is no point in making 2 futures if you are throwing away the value of the first one. Might as well just run everything in a single future. – yǝsʞǝla Jan 15 '16 at 10:15
  • This was to stay as close as possible to the given code. In a real application, the first future would produce a value that you actually use. – Rüdiger Klaehn Jan 15 '16 at 10:22
  • 1
    If `map` and `flatMap` accomplish the same things as `onSuccess` (and more, since they can return values), why have the `onSuccess` in the API at all? Is it only for symmetry with `onFailure`? Or are `onSuccess` and `onFailure` lower-level constructs upon which `map` and `flatMap` are implemented under the hood? – Dan Li Jan 16 '16 at 04:22
  • 1
    Examining the source for [Future](https://github.com/scala/scala/blob/2.11.x/src/library/scala/concurrent/Future.scala#L246) shows that this does seem to be the case. – Dan Li Jan 16 '16 at 04:26
9
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.util._

object Main {
  def main(args: Array[String]): Unit = {
    val f1: Future[Int] = Future(0)
    val f2 = f1 andThen {
      case Success(v) =>
        Thread.sleep(10000)
        println("The program waited patiently for this callback to finish.")
      case Failure(e) =>
        println(e)
    }

    Await.ready(f1, Duration.Inf)
    println("F1 is COMPLETED")
    Await.ready(f2, Duration.Inf)
    println("F2 is COMPLETED")
  }
}

prints:

F1 is COMPLETED
The program waited patiently for this callback to finish.
F2 is COMPLETED

Using promises is even more clear:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent._
import scala.util._

object Main {
  def main(args: Array[String]): Unit = {
    val f: Future[Int] = Future(0)
    val p = Promise[Unit]()
    p.future.onSuccess { case _ =>
      println("The program waited patiently for this callback to finish.")
    }
    f.onSuccess { case _ =>
      Thread.sleep(10000)
      p.success(())
    }

    Await.ready(f, Duration.Inf)
    println("F is COMPLETED")
    Await.ready(p.future, Duration.Inf)
    println("P is COMPLETED")
  }
}

prints:

F is COMPLETED
P is COMPLETED
The program waited patiently for this callback to finish.
yǝsʞǝla
  • 16,272
  • 2
  • 44
  • 65
  • 1
    I think promises are a low level API that should not be used if it can be avoided. So the first example using andThen is better. – Rüdiger Klaehn Jan 15 '16 at 10:27
  • Exactly. Promise is what `andThen` uses under the hood the perform synchronization of the `onComplete` handler. – Suma Jan 15 '16 at 11:36
  • In the example with `Promise`, if you sleep before printing `This program ...` it will never print. I think the example has the same problem as the original question - there is nothing waiting for `p.future.onSuccess`. – nedim May 12 '16 at 09:30
  • In the first example, if you are doing `Await.ready(f2, Duration.Inf)` and `f2` is the continuation of `f1`, then why do you need `Await.ready(f1, Duration.Inf)`? – Danny Varod Oct 17 '19 at 15:55
  • @DannyVarod You don't need both Awaits if you just want to wait for everything to complete. However, if you want to see completion of each futures separately, especially that f2 takes much longer than f1, both awaits are useful. – yǝsʞǝla Oct 17 '19 at 23:32