06
Promise queues: you can get() before anyone put()
With a conventional queue you must put a value in before you can take it out. A promise queue removes that ordering:
get() returns a promise right away, and the i-th get is paired with the i-th put whenever it arrives. Internally it is an asynchronous linked list of deferreds — a head promise and a tail resolver.Just as you can attach an observer to a promise before it is resolved, with a promise queue, you can get a promise for the next value in order before that value has been given.
A promise queue: an asynchronous linked list
Call get() or put() in any order. Each pair meets in one cell of the list.
filled = a put arrivedhollow ring = a get is waiting✓ = the get's promise settledhead advances on get(); tail advances on put().
The queue's history appears here.
q/queue — Mark Miller's concurrency strawman
function PromiseQueue() {
var ends = Promise.defer();
this.put = function (value) {
var next = Promise.defer();
ends.resolve({ head: value, tail: next.promise });
ends.resolve = next.resolve;
};
this.get = function () {
var result = ends.promise.get("head");
ends.promise = ends.promise.get("tail");
return result;
};
}Plural · temporal · value
| PromiseQueue | Value | Plural | Temporal |
| queue.get | Getter | Plural | Temporal |
| queue.put | Setter | Plural | Temporal |
Because get and put are free functions in a closure, you can hand get to a consumer and put to a producer: the principle of least authority, and data flows one way. A queue has no notion of termination — that comes later, when two queues become a stream.
A non-blocking semaphore: three connections in a queue
waiting on connections.get() · 0
No one is waiting. Nothing blocks: waiters hold promises.
pool (the queue)
db1
db2
db3
3 free · 0 in use
holding a connection · 0
All connections idle.
waiting
0
working
0 / 3
completed
0
a promise queue as a semaphore over a connection pool
var connections = new Queue();
connections.put(connectToDb());
connections.put(connectToDb());
connections.put(connectToDb());
function work() {
return connections.get()
.then(function (db) {
return workWithDb(db)
.finally(function () {
connections.put(db);
});
});
}- why it mattersPromises come out in the order puts go in, but a later get can settle sooner: put a promise, and its get waits for it while the next get resolves. Try the “resolves sooner” scenario.
- why it mattersA reactive program doesn't block. Instead of a thread stopping at a semaphore, a worker holds a promise for a connection and continues when it resolves.
- why it matters
finallyreturns the resource whether the work succeeded or failed, so the pool can't leak. A single promise is a mutex; a queue of N values is a counting semaphore.