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.
| 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:
| 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.