ppl
Version:
Promise implementation with advanced flow control capabilities.
558 lines (485 loc) • 14.1 kB
JavaScript
var AllSegment, FuncSegment, JoinSegment, Pipeline, RaceSegment, Segment, SplitSegment, State, as_promise, head, ref, tail,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
ref = require('./utils'), head = ref.head, tail = ref.tail;
as_promise = function(thing, data, context) {
var prom;
if (context == null) {
context = {};
}
if ((thing != null ? thing.then : void 0) != null) {
return thing;
} else if (typeof thing === 'function') {
prom = new FuncSegment(thing, void 0);
prom._context = context;
prom._proceed_fulfill(data);
return prom;
} else {
return Pipeline.resolve(thing);
}
};
State = {
Pending: 'pending',
Fulfilled: 'fulfilled',
Rejected: 'rejected'
};
Segment = (function() {
function Segment() {
this._context = {};
this.state = State.Pending;
}
Segment.prototype.context = function(_context) {
this._context = _context;
return this;
};
Segment.prototype.pipe = function(func) {
if (Array.isArray(func)) {
if (func.length === 0) {
return this._pass();
}
return this.pipe(head(func)).pipe(tail(func));
} else if (func === void 0) {
return this._pass();
} else {
return this._pipe(func);
}
};
Segment.prototype.then = function(fulfill, reject) {
return this._pipe(fulfill, reject);
};
Segment.prototype.done = function(fulfill, reject) {
return this.then(fulfill, reject);
};
Segment.prototype["catch"] = function(reject) {
return this._pipe(void 0, reject);
};
Segment.prototype.split = function(map_func) {
if (map_func != null) {
return this.pipe(map_func).split();
}
return this.extend(new SplitSegment());
};
Segment.prototype.map = function(func) {
if (func === void 0) {
return this._pass();
}
return this.split().pipe(func).join();
};
Segment.prototype.all = function(items) {
return this.extend(new AllSegment(items));
};
Segment.prototype.race = function(items) {
return this.extend(new RaceSegment(items));
};
Segment.prototype.join = function(join_func) {
if (this._split_head != null) {
return this._split_head.join(join_func);
}
return this._pipe(join_func);
};
Segment.prototype.extend = function(segment) {
return this._extend(segment);
};
Segment.prototype._extend = function(segment) {
this.next_segment = segment;
segment.prev_segment = this;
segment._context = this._context;
segment._split_head = this._split_head;
switch (this.state) {
case State.Fulfilled:
segment._proceed_fulfill(this._result);
break;
case State.Rejected:
segment._proceed_reject(this._error);
}
return segment;
};
Segment.prototype.toString = function() {
return this.constructor.name + " (state: " + this.state + ", result: " + this._result + ", error: " + this._error + ")";
};
Segment.prototype._proceed_fulfill = function(data) {
return this._fulfill(data);
};
Segment.prototype._proceed_reject = function(error) {
return this._reject(error);
};
Segment.prototype._fulfill = function(_result) {
var ref1;
this._result = _result;
switch (this.state) {
case State.Rejected:
throw 'Pipeline segment cannot be fulfilled, already rejected';
break;
case State.Fulfilled:
throw 'Pipeline segment cannot be fulfilled, already fulfilled';
}
this.state = State.Fulfilled;
return (ref1 = this.next_segment) != null ? ref1._proceed_fulfill(this._result) : void 0;
};
Segment.prototype._reject = function(_error) {
var ref1;
this._error = _error;
if (this.state === State.Rejected) {
throw 'Pipeline segment already rejected!';
}
this.state = State.Rejected;
return (ref1 = this.next_segment) != null ? ref1._proceed_reject(this._error) : void 0;
};
Segment.prototype._pipe = function(fulfill, reject) {
return this.extend(new FuncSegment(fulfill, reject));
};
Segment.prototype._pass = function() {
return this.extend(new Segment());
};
Segment.prototype._clone = function() {
var clone;
clone = new this.constructor();
clone._context = this._context;
if (this.next_segment != null) {
clone.extend(this.next_segment._clone());
}
return clone;
};
Segment.prototype._first = function() {
var current, prev;
current = this;
while (current != null) {
prev = current.prev_segment;
if (prev == null) {
return current;
}
current = prev;
}
return this;
};
Segment.prototype._last = function() {
var current, next;
current = this;
while (current != null) {
next = current.next_segment;
if (next == null) {
return current;
}
current = next;
}
return this;
};
Segment.prototype._dump_pipeline = function() {
var current, out;
out = "Pipeline\n";
current = this._first();
while (current != null) {
if (current === this) {
out += "|* " + current + "\n";
} else {
out += "| " + current + "\n";
}
current = current.next_segment;
}
return out;
};
return Segment;
})();
FuncSegment = (function(superClass) {
extend(FuncSegment, superClass);
function FuncSegment(fulfill_func, reject_func) {
this.fulfill_func = fulfill_func;
this.reject_func = reject_func;
FuncSegment.__super__.constructor.call(this);
}
FuncSegment.prototype._proceed_fulfill = function(data) {
var err, result;
if (this.fulfill_func == null) {
return this._fulfill(data);
}
result = void 0;
try {
result = this.fulfill_func.apply(this._context, [data]);
} catch (error1) {
err = error1;
return this._reject(err);
}
if ((result != null ? result.then : void 0) != null) {
return result.then((function(_this) {
return function(data) {
return _this._fulfill(data);
};
})(this))["catch"]((function(_this) {
return function(error) {
return _this._reject(error);
};
})(this));
} else {
return this._fulfill(result);
}
};
FuncSegment.prototype._proceed_reject = function(error) {
var err, result;
if (this.reject_func == null) {
return this._reject(error);
}
try {
result = this.reject_func.apply(this._context, [error]);
} catch (error1) {
err = error1;
return this._reject(err);
}
if ((result != null ? result.then : void 0) != null) {
return result.then((function(_this) {
return function(data) {
return _this._fulfill(data);
};
})(this))["catch"]((function(_this) {
return function(error) {
return _this._reject(error);
};
})(this));
} else {
return this._reject(result);
}
};
FuncSegment.prototype._clone = function() {
var clone;
clone = FuncSegment.__super__._clone.call(this);
clone.fulfill_func = this.fulfill_func;
clone.reject_func = this.reject_func;
return clone;
};
return FuncSegment;
})(Segment);
SplitSegment = (function(superClass) {
extend(SplitSegment, superClass);
function SplitSegment() {
SplitSegment.__super__.constructor.call(this);
this.child_pipes = [];
this.joined = false;
}
SplitSegment.prototype.extend = function(segment) {
this.template = segment;
segment._context = this._context;
segment._split_head = this;
return segment;
};
SplitSegment.prototype._proceed_fulfill = function(incoming) {
this.incoming = incoming;
if (!Array.isArray(this.incoming)) {
throw 'Can only split on Array context!';
}
if (this.joined) {
return this._process_children();
}
};
SplitSegment.prototype._process_children = function() {
var i, item, len, ref1, segment;
ref1 = this.incoming;
for (i = 0, len = ref1.length; i < len; i++) {
item = ref1[i];
segment = Pipeline.source(item).context(this._context);
if (this.template != null) {
segment = segment.extend(this.template._clone());
}
this.child_pipes.push(segment._last());
}
return this._fulfill(this.incoming);
};
SplitSegment.prototype.join = function(join_func) {
var segment;
segment = this._extend(new JoinSegment(this));
if (join_func != null) {
segment = segment.pipe(join_func);
}
this.joined = true;
if (this.incoming != null) {
this._process_children();
}
return segment;
};
SplitSegment.prototype._clone = function() {
var clone, join;
clone = new SplitSegment();
clone._context = this._context;
if (this.template != null) {
clone.template = this.template._clone();
}
if (this.next_segment && this.next_segment.constructor.name === 'JoinSegment') {
join = this.next_segment._clone();
clone._extend(join);
clone.joined = true;
join.split_segment = clone;
}
return clone;
};
return SplitSegment;
})(Segment);
JoinSegment = (function(superClass) {
extend(JoinSegment, superClass);
function JoinSegment(split_segment) {
this.split_segment = split_segment;
JoinSegment.__super__.constructor.call(this);
}
JoinSegment.prototype._proceed_fulfill = function(data) {
var pipes, process, results;
results = [];
pipes = this.split_segment.child_pipes;
process = (function(_this) {
return function() {
var pipe;
pipe = pipes.shift();
if (pipe == null) {
return _this._fulfill(results);
}
return pipe.then(function(result) {
results.push(result);
return process();
})["catch"](function(err) {
return _this._reject(err);
});
};
})(this);
return process();
};
return JoinSegment;
})(Segment);
AllSegment = (function(superClass) {
extend(AllSegment, superClass);
function AllSegment(items1) {
this.items = items1;
AllSegment.__super__.constructor.call(this);
}
AllSegment.prototype._proceed_fulfill = function(data) {
var item, process, promises, results;
results = [];
promises = (function() {
var i, len, ref1, results1;
ref1 = this.items;
results1 = [];
for (i = 0, len = ref1.length; i < len; i++) {
item = ref1[i];
results1.push(as_promise(item, data, this._context));
}
return results1;
}).call(this);
process = (function(_this) {
return function() {
var promise;
promise = promises.shift();
if (promise == null) {
return _this._fulfill(results);
}
promise.then(function(result) {
results.push(result);
return process();
});
return promise["catch"](function(err) {
return _this._reject(err);
});
};
})(this);
return process();
};
AllSegment.prototype._clone = function() {
var clone;
clone = AllSegment.__super__._clone.call(this);
clone.items = this.item;
return clone;
};
return AllSegment;
})(Segment);
RaceSegment = (function(superClass) {
extend(RaceSegment, superClass);
function RaceSegment(items1) {
this.items = items1;
RaceSegment.__super__.constructor.call(this);
}
RaceSegment.prototype._proceed_fulfill = function(data) {
var complete, i, item, len, promise, promises, results, results1;
results = [];
promises = (function() {
var i, len, ref1, results1;
ref1 = this.items;
results1 = [];
for (i = 0, len = ref1.length; i < len; i++) {
item = ref1[i];
results1.push(as_promise(item, data, this._context));
}
return results1;
}).call(this);
if (!(promises != null ? promises.length : void 0)) {
return this._fulfill(void 0);
}
complete = false;
results1 = [];
for (i = 0, len = promises.length; i < len; i++) {
promise = promises[i];
results1.push(promise.then((function(_this) {
return function(result) {
if (complete) {
return;
}
complete = true;
return _this._fulfill(result);
};
})(this))["catch"]((function(_this) {
return function(err) {
if (complete) {
return;
}
complete = true;
return _this._reject(err);
};
})(this)));
}
return results1;
};
RaceSegment.prototype._clone = function() {
var clone;
clone = RaceSegment.__super__._clone.call(this);
clone.items = this.item;
return clone;
};
return RaceSegment;
})(Segment);
Pipeline = (function(superClass) {
extend(Pipeline, superClass);
function Pipeline(callback) {
Pipeline.__super__.constructor.call(this);
callback((function(_this) {
return function(data) {
return _this._fulfill(data);
};
})(this), (function(_this) {
return function(err) {
return _this._reject(err);
};
})(this));
}
Pipeline.source = function(data) {
var segment;
segment = new Segment();
segment._fulfill(data);
return segment;
};
Pipeline.resolve = function(data) {
return this.source(data);
};
Pipeline.reject = function(error) {
var segment;
segment = new Segment();
segment._reject(error);
return segment;
};
Pipeline.all = function(items) {
var segment;
segment = new AllSegment(items);
segment._proceed_fulfill();
return segment;
};
Pipeline.race = function(items) {
var segment;
segment = new RaceSegment(items);
segment._proceed_fulfill();
return segment;
};
return Pipeline;
})(Segment);
module.exports = Pipeline;