Futures, Promises, Deferreds
Wikipedia says
that a future
is a proxy for an unknown, incomplete computation. While
this definition sufficies as a terse description of my current
understanding of what a future
is, I initially encountered the concept
while using jQuery, where it is known as
deferred
. Below, when I use
JavaScript examples, deferred
and Promise
will be mostly synonmous
(deferred
s are a jQuery specific thing). When I show Scala examples,
Future
s and Promise
s are different, as I will explain.
First Encountering Deferreds
I first encountered it as a means of addressing callback hell. It was common to see code using nested callbacks:
$.ajax('http://.../1', {
complete: function() {
$.ajax('http://.../2', {
complete: function() {
$.ajax('http://.../3', {
complete: function() {
$('#id').hide();
}
}
}
});
}
});
We can rewrite the code using deferred
s like so:
$.ajax('http://.../1')
.then(function() {
return $.ajax('http://.../2');
})
.then(function() {
return $.ajax('http://.../3');
})
.then(function() {
$('#id').hide();
});
Just this benefit was enough for me to start using them, but at the time I didn’t realize some of the other benefits.
Using Promise
s
Promise
s are a construct to generalize the idea of asynchronous
results. For example, let’s say we want to:
- Perform an XHR
- Hide an element with a delay
- Create some element on the page
The rest of the examples will use ES2017 syntax. Using jQuery:
$.ajax('http://.../1')
.then(() => new Promise((resolve, reject) =>
$('#id').hide(5000, () => resolve())))
.then(() => $('body').append('<div>Hello World</div>'))
Above, we link different types of events in a general way, by using
deferred. $.ajax
natively returns a deferred, which supports the
Promise
interface.
We manually create a Promise
so that we can use
the Promise
interface with $.hide
.
We can also do cool things like delay the run of a function using a
Promise
by using setTimeout
:
let delay = (millis) => new Promise((resolve, reject) =>
setTimeout(millis, () => resolve()))
delay(1000)
.then(() => $('#id').hide())
Next, when we use promises, we can unify error handling. We take the example before and add some error checking.
$.ajax('http://.../1')
.then(() => new Promise((resolve, reject) => {
let el = $('#id');
if(el.length) {
$('#id').hide(5000, () => resolve())
}
else {
reject('no_such_element');
}
}))
.then(() => {
$('body').append('<div>Hello World</div>');
// some checking
throw 'some_error';
})
.catch(e => {
switch(e) {
case 'no_such_element':
// handle accordingly
break;
case 'some_error':
// handle accordingly
break;
default:
// handle error we didn't expect
break;
}
});
Using callbacks, this type of handling would be more convulted, requiring either that you have each calling function handle the exceptions of the callee, or wrap each callback in some sort of composing error handler function. This is much more straightforward.
Arguably the biggest benefit of Promise
s is that they are easy to
compose. Let’s say we need the results of two XHR
s:
let result1 = $.ajax('http://.../1')
let result2 = $.ajax('http://.../2')
Promise.all([result1, result2])
.then((all) => {
let [data1, data2] = all
// do something with the data
})
or that one request depends on the other:
$.ajax('http://.../1')
.then((data1) =>
$.ajax('http://.../1/' + data1.key)
// we must combine the data here assuming we need both later
.then((data2) => [data1, data2]))
.then((all) => {
let [data1, data2] = all
// do something with the data
})
Some other useful things you can do with promises:
// Turn a value into a `Promise`
Promise.resolve(1)
// Turn a value into a rejected promise
Promise.reject('error')
// Return the first completed promise
Promise.race([xhr1, xhr2, xhr3])
Promise
s along with generators also enable JavaScript’s await ...
async syntax
.
Scala Futures
Although Java has something called a Future
, it’s not composable in
the sense above. (Java 8 added CompletableFuture
which is more similar
to the Future
s we are talking about.)
Scala has one that is composable: scala.concurrent.Future
. Creating a
Future
is similar to how it is done in JavaScript:
Future {
/// some async computation
}
The method definition looks like:
def apply[T](body: ⇒> T)(implicit executor: ExecutionContext): Future[T]
Which roughly means that the Future.apply
function or just Future
takes function returning a T
, and also implicit executor
. There are
a few things to unpack here. First, since Scala is a typed language, we
should declare our types. Here, we have some generic type T
. The type
is tied to the return value of the function we pass to the Future
function. For example, a Future { 1 }
would be a Future[Int]
.
Scala has something called implicit
values. Although implicits have
many uses, when you see them in a method definition, they usually have
to do with the context of the method. In this case, we have want to have
an ExecutionContext
whenever we create a Future
this way.
Finally, what is an ExecutionContext
? In JavaScript, things are
single-threaded and our asynchronous model is based on events. There’s
jsut a single-thread on which all events are processed as they complete.
On the JVM, although there are event-based APIs like java.nio
, the
typical model of asynchronicity is thread-based. The ExecutionContext
describes how you want to run the Future
. Typically, you will run
Future
s on some sort of thread pool.
One of the most confusing differences for me between JavaScript and
Scala is that Scala has both a Future
and a Promise
. When I first
learned about Scala futures, I always wondered what the use of a
Promise
was. The key insight is that a Future
represents a value
which has already begun computation. There is no way to cancel it. It
will attempt to be computed. For this reason, you sometimes need a way
to create a Future
without starting the computation. This could be
because you are returning a Future
to a caller, but do not want them
to have any control over its execution.
class SchedulerWrapper implements Runnable {
private val internalWorker = ???
def addWork[T](work: () => T): Future[T] = {
val promise = Promise[T]()
internalWorker.schedule {
promise.complete(Try{ work() })
}
promise.future
}
}
In this example, have some sort of internal worker that we don’t have
control of. It has a schedule function: def schedule(f: => Unit)
that
it will try to schedule to run in some way. If we want to return a
Future
from out addWork
function, a promise will be the best way to
do this.
The idea that a Scala Future is already started is important because the
Future
equivalents of other languages, such as C#, are more general in
that they do not start automatically. C# has
Task
s
as part of the standard library, which
do not have to immediately start. The
Scalaz
library
provides something analagous to this.
Another big difference between Scala Future
s and JavaScript Promise
s
are how you can apply a function to the results. In JavaScript, you can
do this:
let promise1 = ...
let promise2 = ...
// returns a promise containing `something(data)`
let promise3a = promise1.then((data) => something(data))
// returns a promise containing the results of `promise2`
let promise3b = promise1.then(() => promise2)
In Scala, it’s a little different. We can’t use the same method to
operate both on T
s and Future[T]
s.
val future1 = getFuture1
val future2 = getFuture2
val future3a = future1.map(data => something(data))
val future3b = future1.flatMap(_ => future2)
In Scala, we can use an alternative syntax called a for-comprehension
that some people may find more readable.
val future1 = getFuture1
val future2 = getFuture2
val future3a = for {
data <- future1
} yield something(data)
val future3b = for {
_ <- future1
result2 <- future2
} yield result2
Note that these Scala examples above are different than something like
val future3b = for {
_ <- getFuture1
result2 <- getFuture2
} yield result2
which delays any work on retrieving result2
until the first future has
completed.
Conclusion
Futures or promises, whatever it is you call them, are an interesting
way to get out of callback hell. What’s more, they are a simple way to
add asyncronicity to code. Although using them at first may be a bit
tricky, experience with them will lead to a clear intuition about how
they work. They’ll come a point where you will even know intuitively how
to implement them. Especially if you are experienced with JavaScript,
after some study, that language’s version should be straightfoward to
implement. A good first step is to think about the semantics of then
.
There are two key behaviors:
-
then
maps over the result of aPromise
. -
then
will unwrap aPromise
returned from the function passed.