/// <reference path='../../../third_party/typings/es6-promise/es6-promise.d.ts' />
// This file defines 'handler queues'. These are an abstraction for a stream of
// events (but think of an event as an element of data to be handled) along with
// a stream of functions to handle the events. Each event is guarenteed to be
// handled, and gets a promise for the result of that event being handled. A
// function handling an event may set the function that handles future events,
// or it may stop handling events in which case the event stream will be queued
// until a handler is set.
//
// This is a bit like traditional event handlers, but each thing in the hanlder
// queue must be handled by exactly one handler (although that handler may
// itself call several functions). This allows async assignment of the handler
// along with asynchronous adding of events to the queue.
//
// CONSIDER: How efficient is a new Error? Maybe best to have rejection
// without an error since the error is not meaningful.
//
// CONSIDER: there's quite a bit of book-keeping for the resulting promises from
// a handle call. We may want a simpler version of this class that doesn't need
// to remember result values (those of type Result).
//
// CONSIDER: This is kind of similar to functional parsing. May be good to
// formalize the relationship in comments here.
var baseQueue = require('../queue/queue');
// The |HandlerQueueStats| class contains increment-only counters
// characterizing the state and history of a |QueueHandler|.
var HandlerQueueStats = (function () {
function HandlerQueueStats() {
// Total events ever input by the HandlerQueue. Intent:
//
// queued_events + immediately_handled_events + rejected_events =
// total_events.
//
// queued_events - queued_handled_events - rejected_events = number
// of events in queue right now.
this.total_events = 0;
// Ever-queued-events
this.queued_events = 0;
// Events that were immediately handled (b/c there was a handler set).
this.immediately_handled_events = 0;
// Events that were handled after going through the queue.
this.queued_handled_events = 0;
// Number of events rejected in a queue clear.
this.rejected_events = 0;
// Number of times a handler was set on this queue (the handler was
// previously null).
this.handler_set_count = 0;
// Number of times a handler was changed on this queue (the handler
// was previously non-null).
this.handler_change_count = 0;
// Number of times a handler was un-set on this queue (when then
// handler previously non-null).
this.handler_clear_count = 0;
// Number of times we set a new handler while we have an existing
// promise, causing a rejection of that promise.
this.handler_rejections = 0;
}
return HandlerQueueStats;
})();
exports.HandlerQueueStats = HandlerQueueStats;
// Internal helper class. Holds an object called |thing| of type |T| and
// provides a promise for a new result object of type |T2|. The idea is that
// |T2| is will be result of some async function applied to |thing|. This
// helper supports the function that gerates the new object to be known async
// (i.e. later), but still being able to promise a promise for the result
// immidiately.
//
// Assumes fulfill/reject are called exclusively and only once.
var PendingPromiseHandler = (function () {
function PendingPromiseHandler(thing) {
var _this = this;
this.thing = thing;
this.reject = function (e) {
Iif (_this.completed_) {
console.error('reject must not be called on a completed promise.');
return;
}
_this.completed_ = true;
_this.reject_(e);
};
this.handleWith = function (handler) {
Iif (_this.completed_) {
console.error('handleWith must not be called on a completed promise.');
return;
}
_this.completed_ = true;
handler(_this.thing).then(_this.fulfill_);
};
// This holds the T object, and fulfills with a T2 when fulfill is called
// with T2. The point is we can give back the promise now but fulfill can
// be called later.
this.promise = new Promise(function (F, R) {
_this.fulfill_ = F;
_this.reject_ = R;
});
this.completed_ = false;
}
return PendingPromiseHandler;
})();
// The |Queue| class provides a |QueueFeeder| and a |QueueHandler|. The idea is
// that the QueueHandler processes inputs of type |Feed| from the |QueueFeeder|
// and gives back promises for objects of type |Result|. The handle function
// takes objects of type |Feed| and promises objects of type |Result| (the
// handler may run asynchonously).
//
// When the handler is set to |null|, objects are queued up to be handled. When
// the handler is set to not a non-null function, everything in the queue is
// handled by that function. There are some convenience functions for stopping
// Handling, and hanlding just the next event/element.
//
// CONSIDER: this is a kind of co-promise, and can probably be
// extended/generalized.
var Queue = (function () {
// CONSIDER: allow queue to be size-bounded? Reject on too much stuff?
function Queue() {
var _this = this;
// The queue of things to handle.
this.queue_ = new baseQueue.Queue();
// Handler function for things on the queue. When null, things queue up.
// When non-null, gets called on the thing to handle. When set, called on
// everything in the queue in FIFO order.
this.handler_ = null;
// We store a handler's promise rejection function and call it when
// `setHandler` is called and we had a previously promised handler. We need
// to do this because the old handler would never fulfill the old promise as
// it is no longer attached.
this.rejectFn_ = null;
// Handler statistics.
this.stats_ = new HandlerQueueStats();
this.getLength = function () {
return _this.queue_.length;
};
this.isHandling = function () {
return _this.handler_ !== null;
};
this.getStats = function () {
return _this.stats_;
};
// handle or queue the given thing.
this.handle = function (x) {
_this.stats_.total_events++;
if (_this.handler_) {
_this.stats_.immediately_handled_events++;
return _this.handler_(x);
}
var pendingThing = new PendingPromiseHandler(x);
_this.queue_.push(pendingThing);
_this.stats_.queued_events++;
return pendingThing.promise;
};
// Run the handler function on the queue until queue is empty or handler is
// null. Note: a handler may itself setHandler to being null, doing so
// should pause proccessing of the queue.
this.processQueue_ = function () {
while (_this.handler_ && _this.queue_.length > 0) {
_this.stats_.queued_handled_events++;
_this.queue_.shift().handleWith(_this.handler_);
}
};
// Clears the queue, and rejects all promises to handle things on the queue.
this.clear = function () {
while (_this.queue_.length > 0) {
var pendingThing = _this.queue_.shift();
_this.stats_.rejected_events++;
pendingThing.reject(new Error('Cleared by Handler'));
}
};
// Calling setHandler with null pauses handling and queues all objects to be
// handled.
//
// If you have an unfulfilled promise, calling setHandler rejects the old
// promise.
this.setHandler = function (handler) {
Iif (!handler) {
throw new Error('handler must not be null');
}
Iif (_this.rejectFn_) {
_this.stats_.handler_rejections++;
_this.rejectFn_(new Error('Cancelled by a call to setHandler'));
_this.rejectFn_ = null;
}
Eif (_this.handler_ === null) {
_this.stats_.handler_set_count++;
}
else {
_this.stats_.handler_change_count++;
}
_this.handler_ = handler;
_this.processQueue_();
};
// Convenience function for handler to be an ordinary function without a
// promise result.
this.setSyncHandler = function (handler) {
_this.setHandler(function (x) {
return Promise.resolve(handler(x));
});
};
// Reject the previous promise handler if it exists and stop handling stuff.
this.stopHandling = function () {
if (_this.rejectFn_) {
_this.stats_.handler_rejections++;
_this.rejectFn_(new Error('Cancelled by a call to setHandler'));
_this.rejectFn_ = null;
}
if (_this.handler_) {
_this.handler_ = null;
_this.stats_.handler_clear_count++;
}
};
// A convenience function that takes a T => Promise<Result> function and sets
// the handler to a function that will return the promise for the next thing
// to handle and then unset the handler after that so that only the next
// thing in the queue is handled.
//
// Note: this sets the Handler to fulfill this promise when there is
// something to handle.
this.setNextHandler = function (handler) {
return new Promise(function (F, R) {
_this.setHandler(function (x) {
// Note: we don't call stopHandling() within this handler because that
// would reject the promise we're about to fulfill.
_this.handler_ = null;
_this.rejectFn_ = null;
_this.stats_.handler_clear_count++;
var resultPromise = handler(x);
resultPromise.then(F);
return resultPromise;
});
if (_this.handler_) {
// If |handler| has not already run, and removed itself, leave a
// rejection function behind as well.
_this.rejectFn_ = R;
}
});
};
// Convenience function for handling next element with an ordinary function.
this.setSyncNextHandler = function (handler) {
return _this.setNextHandler(function (x) {
return Promise.resolve(handler(x));
});
};
}
return Queue;
})();
exports.Queue = Queue; // class Queue
|