This package provides a concurrently safe composable placeholder type for the Go programming language.
While concurrency in Go programs are generally managed using channels, and this is a good model for a great many workflows, it is difficult at times to manage concurrent workflows which are constructed dynamically using this model. Channels need to have known readers, writers, and passing them around and composing them is difficult.
Promises, meanwhile, provide a very natural way to define a parallel program as a series of composable operations and merge points. This implementation of promises enables programs written in Go to use this model when it is a better fit.
p := promise.Promise()
squared := p.Then(func(value interface{}) interface{} {
val, _ := value.(int)
return val * val
})
cubed := squared.Then(func(value interface{}) interface{} {
val, _ := value.(int)
return val * val * val
})
// The above code will now execute.
promise.Complete(2)
Promises provide a way to defer computation until a future state transition, but to compose those computations eagerly into a chain of operations which will run at a later point in time. In this API, there are two basic types of compositions. Then
compositions modify a value when it becomes available, and produce a promise for this modified value. Combine
compositions can construct a new promise after observing the value associated with the promise, and produce another promise which is completed when the returned promise is completed.
// This returns a new ``CompletedPromise``, but it could also return a
// ``CompletablePromise`` instead.
combined := p.Combine(func(value interface{}) Thenable {
val, _ := value.(int)
return Completed(val + 3)
})
There are three types of promises, each of which implements the Thenable
interface.
Type | Constructor | Description |
---|---|---|
CompletablePromise |
|
The composable placeholder for a value which will be made available at a later time. This is the primary type of Promise used by most programs. |
CompletedPromise |
|
A promise whose value is already available. The value |
RejectedPromise |
|
A promise which is in a failed state, the cause |
Both Rejected and Completed promises are already in a completed state when they are initialized. Completable promises, conversely, are initialized in an incomplete state, and they therefore encapsulate one of two possible state transitions.
The most obvious and natural state transition is that some deferred or asynchronous computation produces a value, completing the promise. This is achieved by invoking the Complete
method. Completion transitions the promise from a state of being incomplete to being completed, and execute any composed computations which may depend on the value of this promise. The execution coincidentally happens from within the goroutine which produced the value. All promises which have been created by composing a computation with the promise being completed will thereafter be completed as well, executing their computations and completing any promises which may have been composed from those executions, et cetera.
The less obvious but nevertheless rather important alternate state transition is to reject a promise. This is a means for handling errors within chains of promises. Once the Reject
method has been called on a CompletablePromise
, any composed promises which depend on that promise are also rejected.
In both cases, once the promise transitions to a completed state, it can not transition again and any attempt to do so is a fatal error. Further, it afterward becomes a CompletedPromise
or RejectedPromise
, respectively, and any invocation of either the Then
, Combine
or Catch
methods produce promises of those types. This is largely unimportant for the user of the API, but it does have the implication that the computations are at that point actually executed by the goroutine which invokes the Then
, Combine
or Catch
methods, respectively.
For most well written programs using promises, where the composed computations actually run is completely inconsequential.
The Combine
and Then
operations can be used to compute values or compose computations of values, and that's great — but what happens when the process is done? And what happens when the result of the computation needs to be integrated with a tool-chain which doesn't use promises?
Every implementation of Theanble
has a Get()
method, which returns a the result of the computation. It's signature is Get() (interface{}, error)
, and it returns either the value of the computation, or optionally, an error. If the promise is a CompletablePromise
and it is in an incomplete state, the method blocks until the promise is either Completed
or Rejected
.
This software is Copyright © 2016 Quantcast Corporation, and is provided under the MIT license. See the LICENSE
file for details.