operations
Version:
A library for managing complex chains of asynchronous operations in Javascript.
198 lines (173 loc) • 5.56 kB
JavaScript
var _ = require('underscore');
var log = require('./log');
var Logger = log.loggerWithName('OperationQueue');
function OperationQueue() {
if (!this) {
return new (Function.prototype.bind.apply(OperationQueue, arguments));
}
var self = this;
if (arguments.length) {
if (typeof(arguments[0]) == 'number') {
this.maxConcurrentOperations = arguments[0];
}
else {
this.name = arguments[0];
this.maxConcurrentOperations = arguments[1];
}
}
this._queuedOperations = [];
this._runningOperations = [];
this._running = false;
this._onStart = [];
this._onStop = [];
this.logLevel = null;
Object.defineProperty(this, 'numRunningOperations', {
get: function () {
return self._runningOperations.length;
},
configurable: true,
enumerable: true
});
Object.defineProperty(this, 'loggingOveridden', {
get: function () {
if (self.logLevel) {
return self.logLevel <= log.Level.info;
}
return false;
},
enumerable: true,
configurable: true
})
}
OperationQueue.prototype._nextOperations = function () {
var self = this;
while ((self._runningOperations.length < self.maxConcurrentOperations) && self._queuedOperations.length) {
var op = self._queuedOperations[0];
self._queuedOperations.splice(0, 1);
self._runOperation(op);
}
};
OperationQueue.prototype._runOperation = function (op) {
var self = this;
for (var i = 0; i < this._queuedOperations.length; i++) {
if (this._queuedOperations[i] == op) {
this._queuedOperations.splice(i, 1);
break;
}
}
this._runningOperations.push(op);
op.onCompletion(function () {
var idx = self._runningOperations.indexOf(op);
self._runningOperations.splice(idx, 1);
if (self._running) {
self._nextOperations();
}
self._logStatus();
});
op.start();
this._logStatus();
};
OperationQueue.prototype._logStatus = function () {
var logFunc = this._getLogFunc();
if (Logger.info.isEnabled || this.loggingOveridden) {
var numRunning = this.numRunningOperations;
var numQueued = this._queuedOperations.length;
var name = this.name || "Unnamed Queue";
if (numRunning && numQueued) {
logFunc('"' + name + '" now has ' + numRunning.toString() + ' operations running and ' + numQueued.toString() + ' operations queued');
}
else if (numRunning) {
logFunc('"' + name + '" now has ' + numRunning.toString() + ' operations running');
}
else if (numQueued) {
logFunc('"' + name + '" now has ' + numQueued.toString() + ' operations queued');
}
else {
logFunc('"' + name + '" has no operations running or queued');
}
}
};
OperationQueue.prototype._logStart = function () {
var logFunc = this._getLogFunc();
if (Logger.info.isEnabled || this.loggingOveridden) {
var name = this.name || "Unnamed Queue";
logFunc('"' + name + '" is now running');
}
};
OperationQueue.prototype._getLogFunc = function () {
if (this.logLevel) {
return _.bind(Logger.override, Logger, log.Level.info, this.logLevel);
}
return Logger.info;
};
OperationQueue.prototype._logStop = function () {
var logFunc = this._getLogFunc();
if (Logger.info.isEnabled || this.loggingOveridden) {
var name = this.name || "Unnamed Queue";
logFunc('"' + name + '" is no longer running');
}
};
OperationQueue.prototype._addOperation = function (op) {
if (this.numRunningOperations < this.maxConcurrentOperations && this._running) {
this._runOperation(op);
}
else {
this._queuedOperations.push(op);
}
this._logStatus();
};
OperationQueue.prototype.addOperation = function (operationOrOperations) {
var self = this;
if (Object.prototype.toString.call(operationOrOperations) === '[object Array]') {
_.each(operationOrOperations, function (op) {self._addOperation(op)});
}
else {
this._addOperation(operationOrOperations);
}
};
OperationQueue.prototype.start = function () {
var self = this;
var wasRunning = this._running;
this._running = true;
if (!wasRunning) {
_.each(self._onStart, function (c) {
_.bind(c, self)();
});
self._nextOperations();
self._logStart();
}
};
OperationQueue.prototype.stop = function (cancel) {
var self = this;
var wasRunning = this._running;
this._running = false;
if (wasRunning) {
if (cancel) {
var operations = this._runningOperations.slice(0); // Clone so not fighting callbacks.
_.each(operations, function (o) {
o.cancel();
});
}
self._logStop();
_.each(self._onStop, function (c) {
_.bind(c, self)();
});
}
};
OperationQueue.prototype.onStart = function (o) {
this._onStart.push(o);
};
OperationQueue.prototype.onStop = function (o) {
this._onStop.push(o);
};
Object.defineProperty(OperationQueue, 'logLevel', {
get: function () {
return Logger.currentLevel();
},
set: function (v) {
Logger.setLevel(v);
},
configurable: true,
enumerable: true
});
module.exports.OperationQueue = OperationQueue;