These days a common bottleneck in execution time is the network. It simply takes a long time to make the round trip (several milliseconds, versus a 100th of that to process the result). So, if you're doing multiple network requests, it makes sense to do them in parallel to reduce the overall latency. Futures/promises are one technique to accomplish this.
While there are more technical definitions, a future indicates you'll need something (usually the result of a network request) in the future and you'd like to ask for it now so it can be fetched in the background. Or in other terms, you'd like to make an asynchronous request in the background.
The future/promise pattern is implemented in many languages. For example, JQuery implements a deferred object and futures are built into Scala. In the land of Golang the goroutine and channel concurrency primitives can be used to build up the functionality. A simple implementation is shown below.
RequestFuture function returns a channel right away, while the actual http request
is done asynchronously in a goroutine. The main function can continue doing other things,
like trigger other futures. When the result is needed, we read the result from the channel.
If the request isn't finished yet,
body := <- future will block until the result is ready.
This keeps the intentions of the main function clean and easy to read.
However, there is a limitation: The error value is lost. In the above example, if there is an error, the body will be nil/empty. But, since channels can only return one value, you'll need to create a separate struct type to wrap both values and return that via the channel.
An alternate solution is to more or less wrap the above example in a function and return that instead.
The function created by
RequestFutureFunction returns two values, solving the limitation
of the first example. Usage is similar to above.
An added benefit of this approach is
future() can be called multiple times and will
always return the same result. Something that isn't possible with the first solution, as only
one value is pushed onto the returned channel.
But wow, if you want to do this for many different asynchronous functions, you're in for a lot of boilerplate code. We can fix a bit of that by abstracting the functionality into a helper:
The usage moves some of the http request functionality into the main function, but allows for much cleaner code when calling multiple tasks as futures.
A lot of the tricky bits of working with channels is reused by calling the
However, to make the use generic, there is a cast from
If done incorrectly this would cause a panic at runtime. This is an example of where a
"generic" type would help, allowing the compiler to validate things at compile times.
At Strava, we're attempting to solve this issue by creating the "future functions" using a code generator. For example, if there is a function like:
A "future" version is automatically generated using a template similar to the code above:
Once generated, developers using
GetActivityFuture get the benefits of type
safety at compile time.
Obviously, building a separate code generator has its own overhead, but we're generating a number of helpers, so it's not single purpose (more on that in other blog posts).
We strive to solve and iterate on solutions to interesting problems. Our use of futures in Go is just one example. If you'd like to work on more see our job openings.
Gopher by Renee French and licensed under CC BY 3.0 US