Futures and Promises

Basically, Promise is where Future comes from; it allows an API surface to provide an “empty” future that can be completed, behind the scenes, at some point in the future. The code that calls the API doesn’t know, or need to know, when or how the future will be fulfilled. Likewise, the code that supplies the value doesn’t need to track who is currently holding on to the future, which makes it mostly straightforward for us to use. Promise[T]() creates a new promise, and then promise.future, which returns a Future[T] that we can pass on to our caller. When we call promise.success or promise.failure, the Future will succeed or fail as well.

For example, we can use it, along with libuv’s TimerHandle type we worked with in the previous chapter, to implement the delay() function.

LibUVFutures/timer_async/timer.scala
 object​ Timer {
 
 var​ serial ​=​ 0L
 var​ timers ​=​ mutable.HashMap[​Long​,​Promise​[​Unit​]]()
 
 def​ delay(dur​:​​Duration​)​:​​Future​[​Unit​] ​=​ {
 val​ promise ​=​ Promise[​Unit​]()
  serial += 1
 val​ timer_id ​=​ serial
  timers(timer_id) ​=​ promise
 val​ millis ​=​ dur.toMillis
 
 val​ timer_handle ​=​ stdlib.malloc(uv_handle_size(UV_TIMER_T))
  uv_timer_init(EventLoop.loop,timer_handle)
 val​ timer_data ​=​ timer_handle.asInstanceOf[​Ptr​[​Long​]]
  !timer_data ​=​ timer_id
  uv_timer_start(timer_handle, timerCB, millis, 0)
 
  promise.future
  }
 
 val​ timerCB ​=​ ​new​ TimerCB {
 def​ apply(handle​:​​TimerHandle​)​:​​Unit​ = {
  println(​"callback fired!"​)
 val​ timer_data ​=​ handle.asInstanceOf[​Ptr​[​Long​]]
 val​ timer_id ​=​ !timer_data
 val​ timer_promise ​=​ timers(timer_id)
  timers.remove(timer_id)
  println(s​"completing promise ${timer_id}"​)
  timer_promise.success(())
  }
  }
 }

Here, much like in our web server, we’re generating serial numbers for each active timer—because the serial numbers are plain integers, we can store them in the local storage of the TimerHandle, which we can look up in the callback. This allows us to work around a tricky limitation of C interoperability: since we cannot convert any Scala function that captures local state to a static CFuncPtr, we’ll frequently use this technique to give callback functions access to higher-level Scala Promise and Map objects with an intermediate lookup.

Now, with the implementation out of the way, the actual main function to use our timers is remarkably streamlined:

LibUVFutures/timer_async/main.scala
 def​ main(args​:​​Array​[​String​])​:​​Unit​ = {
  println(​"hello"​)
 implicit​ ​val​ loop ​=​ EventLoop
  println(​"setting up timer"​)
  Timer.delay(2.seconds).map { ​_​ ​=>
  println(​"timer done!"​)
  }
  println(​"about to invoke loop.run()"​)
  loop.run()
  println(​"done!"​)
 }

Let’s test it:

 $ ./target/scala-2.11/async_timer-out
 hello
 setting up timer
 about to invoke loop.run()
 callback fired!
 completing promise 1
 adding task to queue
 executing task
 timer done!
 task queue empty, stopping queue
 done!

Looks good! This same implementation technique will serve us well throughout the book, even for much more powerful libraries.