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
PromiseQueueValuePluralTemporal
queue.getGetterPluralTemporal
queue.putSetterPluralTemporal

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 mattersfinally returns 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.