Commit 58066429 authored by Romain Courteaud's avatar Romain Courteaud

WIP monitor pattern

parent 58953a31
......@@ -4,6 +4,7 @@ import { Promise } from "./rsvp/promise";
import { denodeify } from "./rsvp/node";
import { all, any } from "./rsvp/all";
import { Queue, ResolvedQueueError } from "./rsvp/queue";
import { Monitor, ResolvedMonitorError } from "./rsvp/watcher";
import { delay, timeout } from "./rsvp/timeout";
import { hash } from "./rsvp/hash";
import { rethrow } from "./rsvp/rethrow";
......@@ -16,4 +17,4 @@ function configure(name, value) {
config[name] = value;
}
export { CancellationError, Promise, EventTarget, all, any, Queue, ResolvedQueueError, delay, timeout, hash, rethrow, defer, denodeify, configure, resolve, reject };
export { CancellationError, Promise, EventTarget, all, any, Queue, ResolvedQueueError, Monitor, ResolvedMonitorError, delay, timeout, hash, rethrow, defer, denodeify, configure, resolve, reject };
import { Promise } from "./promise";
import { Queue } from "./queue";
import { CancellationError } from "./cancellation_error";
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
function ResolvedMonitorError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedMonitorError.prototype = new Error();
ResolvedMonitorError.prototype.constructor = ResolvedMonitorError;
function isFunction(x){
return typeof x === "function";
}
var Monitor = function() {
var monitor = this,
promise_list = [],
promise,
reject,
notify,
resolved;
if (!(this instanceof Monitor)) {
return new Monitor();
}
function canceller() {
var len = promise_list.length,
i;
for (i = 0; i < len; i += 1) {
promise_list[i].cancel();
}
// Clean it to speed up other canceller run
promise_list = [];
}
promise = new Promise(function(done, fail, progress) {
reject = function (rejectedReason) {
if (resolved) {return;}
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason ;
resolved = true;
canceller();
return fail(rejectedReason);
};
notify = progress;
}, canceller);
monitor.cancel = function () {
if (resolved) {return;}
resolved = true;
promise.cancel();
promise.fail(function (rejectedReason) {
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
});
};
monitor.then = function () {
return promise.then.apply(promise, arguments);
};
monitor.monitor = function(promise_to_monitor) {
if (resolved) {
throw new ResolvedMonitorError();
}
var queue = new Queue()
.push(function () {
return promise_to_monitor;
})
.push(function (fulfillmentValue) {
// Promise to monitor is fullfilled, remove it from the list
var len = promise_list.length,
promise_to_monitor,
new_promise_list = [],
i;
for (i = 0; i < len; i += 1) {
promise_to_monitor = promise_list[i];
if (!(promise_to_monitor.isFulfilled ||
promise_to_monitor.isRejected)) {
new_promise_list.push(promise_to_monitor);
}
}
promise_list = new_promise_list;
}, function (rejectedReason) {
if (rejectedReason instanceof CancellationError) {
if (!(promise_to_monitor.isFulfilled && promise_to_monitor.isRejected)) {
// The queue could be cancelled before the first push is run
promise_to_monitor.cancel();
}
}
reject(rejectedReason);
throw rejectedReason;
}, function (notificationValue) {
notify(notificationValue);
return notificationValue;
});
promise_list.push(queue);
return this;
};
};
Monitor.prototype = Object.create(Promise.prototype);
Monitor.prototype.constructor = Monitor;
export { Monitor, ResolvedMonitorError };
......@@ -2294,7 +2294,7 @@ describe("`RSVP.Queue`", function () {
it('should be fulfilled when the queue is empty', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return RSVP.delay(30);
return RSVP.delay(20);
});
setTimeout(function() {
......@@ -2341,7 +2341,7 @@ describe("`RSVP.Queue`", function () {
it('should be rejected if one entry is rejected', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return RSVP.timeout(30);
return RSVP.timeout(20);
});
setTimeout(function() {
......@@ -2439,3 +2439,230 @@ describe("`RSVP.Queue`", function () {
});
});
});
describe("`RSVP.Monitor`", function () {
describe("`ResolvedMonitorError`", function () {
describe("`ResolvedMonitorError` constructor", function () {
it('should exist and have length 1', function () {
assert(RSVP.ResolvedMonitorError);
assert.equal(RSVP.ResolvedMonitorError.length, 1);
});
it('should be a constructor', function () {
var error = new RSVP.ResolvedMonitorError();
assert.equal(
Object.getPrototypeOf(error),
RSVP.ResolvedMonitorError.prototype,
'[[Prototype]] equals ResolvedMonitorError.prototype'
);
assert.equal(
error.constructor,
RSVP.ResolvedMonitorError,
'constructor property of instances is set correctly'
);
assert.equal(
RSVP.ResolvedMonitorError.prototype.constructor,
RSVP.ResolvedMonitorError,
'constructor property of prototype is set correctly'
);
});
it('should throw a `TypeError` if not given a string', function () {
assert.throws(function () {
var error = new RSVP.ResolvedMonitorError({});
// ... jslint forces to use the variable
error.foo = "bar";
}, TypeError);
});
it('should be throwable', function () {
assert.throws(function () {
throw new RSVP.ResolvedMonitorError();
}, RSVP.ResolvedMonitorError);
});
});
describe("`ResolvedMonitorError` instance", function () {
it('must be an instance of Error', function () {
var error = new RSVP.ResolvedMonitorError();
assert(error instanceof Error);
});
it('must have a `name` property with value `resolved`', function () {
var error = new RSVP.ResolvedMonitorError();
assert.equal(error.name, "resolved");
});
it('must have a `message` property with the message value', function () {
var error = new RSVP.ResolvedMonitorError("Foo");
assert.equal(error.message, "Foo");
});
it('must have a default `message` property to `Default Message`',
function () {
var error = new RSVP.ResolvedMonitorError();
assert.equal(error.message, "Default Message");
});
});
});
describe("`Monitor` constructor", function () {
it('should exist and have length 0', function () {
assert(RSVP.Monitor);
assert.equal(RSVP.Monitor.length, 0);
});
it('should be a constructor', function () {
var monitor = new RSVP.Monitor();
assert.equal(
Object.getPrototypeOf(monitor),
RSVP.Monitor.prototype,
'[[Prototype]] equals Monitor.prototype'
);
assert.equal(
monitor.constructor,
RSVP.Monitor,
'constructor property of instances is set correctly'
);
assert.equal(
RSVP.Monitor.prototype.constructor,
RSVP.Monitor,
'constructor property of prototype is set correctly'
);
});
it('should return a new promise', function () {
var promise = new RSVP.Monitor();
assert(promise instanceof RSVP.Monitor);
assert(promise instanceof RSVP.Promise);
});
it('should work without `new`', function() {
var promise = RSVP.Monitor();
assert(promise instanceof RSVP.Monitor);
assert(promise instanceof RSVP.Promise);
});
});
describe("`monitor`", function () {
it('should be a function', function () {
var monitor = new RSVP.Monitor();
assert.equal(typeof monitor.monitor, "function");
});
it('should return the same monitor instance', function () {
var monitor = new RSVP.Monitor(),
result = monitor.monitor();
assert.equal(result, monitor);
});
it('should throw if the monitor is rejected', function (done) {
var monitor = new RSVP.Monitor();
monitor.cancel();
setTimeout(function() {
assert.throws(function () {
monitor.monitor();
}, RSVP.ResolvedMonitorError);
done();
}, 20);
});
});
describe("`Monitor` instance", function () {
it('should not be rejected without any call', function (done) {
var monitor = new RSVP.Monitor();
setTimeout(function() {
assert.equal(monitor.isRejected, undefined);
done();
}, 20);
});
it('a fulfillment does not impact the monitoring', function (done) {
var monitor = new RSVP.Monitor();
monitor.monitor("ok");
setTimeout(function() {
assert.equal(monitor.isFulfilled, undefined);
assert.equal(monitor.isRejected, undefined);
done();
}, 20);
});
it('should be rejected if one entry is rejected', function (done) {
var monitor = new RSVP.Monitor();
monitor.monitor(RSVP.timeout(30));
setTimeout(function() {
assert.equal(monitor.isRejected, undefined);
setTimeout(function() {
assert.equal(monitor.isRejected, true);
done();
}, 20);
}, 20);
});
it('get the rejectedReason from the rejected entry', function (done) {
var monitor = new RSVP.Monitor();
monitor.monitor(RSVP.timeout(1));
setTimeout(function() {
assert.equal(monitor.isRejected, true);
assert.equal(monitor.rejectedReason, "Timed out after 1 ms");
done();
}, 20);
});
it('reject cancels the remaining promise', function (done) {
var monitor = new RSVP.Monitor(),
pushed_result = RSVP.delay(1000);
monitor.monitor(RSVP.timeout(1));
monitor.monitor(pushed_result);
setTimeout(function() {
assert.equal(monitor.isRejected, true);
assert.equal(monitor.rejectedReason, "Timed out after 1 ms");
assert.equal(pushed_result.isRejected, true);
assert.equal(pushed_result.rejectedReason, "cancel: Default Message");
done();
}, 20);
});
it('cancel also cancels the remaining promise', function (done) {
var monitor = new RSVP.Monitor(),
pushed_result = RSVP.delay(1000);
monitor.monitor(pushed_result);
monitor.cancel();
setTimeout(function() {
assert.equal(monitor.isRejected, true);
assert(monitor.rejectedReason instanceof RSVP.CancellationError);
assert.equal(pushed_result.isRejected, true);
assert.equal(pushed_result.rejectedReason, "cancel: Default Message");
done();
}, 20);
});
// it("notify should be propagated until `then`", function (done) {
// var monitor = new RSVP.Monitor(), responses = [];
//
// monitor.then(null, null, function (notification) {
// responses.push(notification);
// });
//
// monitor.monitor(
// new RSVP.Promise(function (resolve, reject, progress) {
// progress("hello");
// })
// );
//
// setTimeout(function () {
// assert.equal(responses[0], "hello");
// done();
// }, 20);
// });
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment