Commit 7086f51f authored by Romain Courteaud's avatar Romain Courteaud

Add RSVP#Queue

Queue allow to control the execution of a list of promises.
Especially, it can cancel the list of promises.

The queue is fulfilled only when all pushed promises are fulfilled.
It gets the latest push promise fulfillmentValue as result.

    var queue;
    queue = RSVP.Queue();
    queue.push(function () {
      return 1;
    };
    queue.push(function (value) {
      return value + 1;
    };
    queue.then(function (value) {
      assert(value === 2);
    };
parent be74db04
...@@ -3,6 +3,7 @@ import { CancellationError } from "./rsvp/cancellation_error"; ...@@ -3,6 +3,7 @@ import { CancellationError } from "./rsvp/cancellation_error";
import { Promise } from "./rsvp/promise"; import { Promise } from "./rsvp/promise";
import { denodeify } from "./rsvp/node"; import { denodeify } from "./rsvp/node";
import { all, any } from "./rsvp/all"; import { all, any } from "./rsvp/all";
import { Queue, ResolvedQueueError } from "./rsvp/queue";
import { delay, timeout } from "./rsvp/timeout"; import { delay, timeout } from "./rsvp/timeout";
import { hash } from "./rsvp/hash"; import { hash } from "./rsvp/hash";
import { rethrow } from "./rsvp/rethrow"; import { rethrow } from "./rsvp/rethrow";
...@@ -15,4 +16,4 @@ function configure(name, value) { ...@@ -15,4 +16,4 @@ function configure(name, value) {
config[name] = value; config[name] = value;
} }
export { CancellationError, Promise, EventTarget, all, any, delay, timeout, hash, rethrow, defer, denodeify, configure, resolve, reject }; export { CancellationError, Promise, EventTarget, all, any, Queue, ResolvedQueueError, delay, timeout, hash, rethrow, defer, denodeify, configure, resolve, reject };
import { Promise } from "./promise";
import { delay } from "./timeout";
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
function ResolvedQueueError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedQueueError.prototype = new Error();
ResolvedQueueError.prototype.constructor = ResolvedQueueError;
var Queue = function() {
var promise = this,
promise_list = [],
fulfill,
reject;
function canceller() {
for (var i = 0; i < promise_list.length; i++) {
promise_list[i].cancel();
}
}
Promise.apply(promise, [function (done, fail) {
fulfill = done;
reject = fail;
}, canceller]);
promise_list.push(delay());
promise_list.push(promise_list[0].then(function () {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill();
}
}));
promise.push = function(done, fail) {
var last_promise = promise_list[promise_list.length - 1],
next_promise;
if (this.isRejected || this.isFulfilled) {
throw new ResolvedQueueError();
}
next_promise = last_promise.then(done, fail);
promise_list.push(next_promise);
// Handle pop
promise_list.push(next_promise.then(function (fulfillmentValue) {
promise_list.splice(0, 2);
if (promise_list.length === 0) {
fulfill(fulfillmentValue);
} else {
return fulfillmentValue;
}
}, function (rejectedReason) {
if (!(promise.isRejected || promise.isFulfilled)) {
reject(rejectedReason);
}
throw rejectedReason;
}));
return this;
};
return promise;
};
Queue.prototype = Object.create(Promise.prototype);
Queue.prototype.constructor = Queue;
export { Queue, ResolvedQueueError };
...@@ -831,7 +831,6 @@ describe("RSVP extensions", function() { ...@@ -831,7 +831,6 @@ describe("RSVP extensions", function() {
specify('resolves an empty array passed to RSVP.any()', function(done) { specify('resolves an empty array passed to RSVP.any()', function(done) {
RSVP.any([]).then(function(result) { RSVP.any([]).then(function(result) {
console.log(result);
assert.equal(result, undefined); assert.equal(result, undefined);
done(); done();
}); });
...@@ -1705,3 +1704,265 @@ describe("`RSVP.timeout`", function () { ...@@ -1705,3 +1704,265 @@ describe("`RSVP.timeout`", function () {
}, 20); }, 20);
}); });
}); });
describe("`RSVP.Queue`", function () {
describe("`ResolvedQueueError`", function () {
describe("`ResolvedQueueError` constructor", function () {
it('should exist and have length 1', function () {
assert(RSVP.ResolvedQueueError);
assert.equal(RSVP.ResolvedQueueError.length, 1);
});
it('should be a constructor', function () {
var error = new RSVP.ResolvedQueueError();
assert.equal(
Object.getPrototypeOf(error),
RSVP.ResolvedQueueError.prototype,
'[[Prototype]] equals ResolvedQueueError.prototype'
);
assert.equal(
error.constructor,
RSVP.ResolvedQueueError,
'constructor property of instances is set correctly'
);
assert.equal(
RSVP.ResolvedQueueError.prototype.constructor,
RSVP.ResolvedQueueError,
'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.ResolvedQueueError({});
// ... jslint forces to use the variable
error.foo = "bar";
}, TypeError);
});
it('should be throwable', function () {
assert.throws(function () {
throw new RSVP.ResolvedQueueError();
}, RSVP.ResolvedQueueError);
});
});
describe("`ResolvedQueueError` instance", function () {
it('must be an instance of Error', function () {
var error = new RSVP.ResolvedQueueError();
assert(error instanceof Error);
});
it('must have a `name` property with value `resolved`', function () {
var error = new RSVP.ResolvedQueueError();
assert.equal(error.name, "resolved");
});
it('must have a `message` property with the message value', function () {
var error = new RSVP.ResolvedQueueError("Foo");
assert.equal(error.message, "Foo");
});
it('must have a default `message` property to `Default Message`',
function () {
var error = new RSVP.ResolvedQueueError();
assert.equal(error.message, "Default Message");
});
});
});
describe("`Queue` constructor", function () {
it('should exist and have length 0', function () {
assert(RSVP.Queue);
assert.equal(RSVP.Queue.length, 0);
});
it('should be a constructor', function () {
var queue = new RSVP.Queue();
assert.equal(
Object.getPrototypeOf(queue),
RSVP.Queue.prototype,
'[[Prototype]] equals Queue.prototype'
);
assert.equal(
queue.constructor,
RSVP.Queue,
'constructor property of instances is set correctly'
);
assert.equal(
RSVP.Queue.prototype.constructor,
RSVP.Queue,
'constructor property of prototype is set correctly'
);
});
it('should return a new promise', function () {
var promise = new RSVP.Queue();
assert(promise instanceof RSVP.Queue);
assert(promise instanceof RSVP.Promise);
});
});
describe("`push`", function () {
it('should be a function', function () {
var queue = new RSVP.Queue();
assert.equal(typeof queue.push, "function");
});
it('should return the same queue instance', function () {
var queue = new RSVP.Queue(),
result = queue.push();
assert.equal(result, queue);
});
it('should throw if the queue is fulfilled', function (done) {
var queue = new RSVP.Queue();
setTimeout(function() {
assert.throws(function () {
queue.push();
}, RSVP.ResolvedQueueError);
done();
}, 20);
});
it('should throw if the queue is rejected', function (done) {
var queue = new RSVP.Queue();
queue.cancel();
setTimeout(function() {
assert.throws(function () {
queue.push();
}, RSVP.ResolvedQueueError);
done();
}, 20);
});
});
describe("`Queue` instance", function () {
it('should be fulfilled without any push call', function (done) {
var queue = new RSVP.Queue();
setTimeout(function() {
assert.equal(queue.isFulfilled, true);
done();
}, 20);
});
it('should be fulfilled when the queue is empty', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return RSVP.delay(30);
});
setTimeout(function() {
assert.equal(queue.isFulfilled, undefined);
setTimeout(function() {
assert.equal(queue.isFulfilled, true);
done();
}, 20);
}, 20);
});
it('get the fulfillmentValue of the last queue entry', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return "foo"
});
setTimeout(function() {
assert.equal(queue.isFulfilled, true);
assert.equal(queue.fulfillmentValue, "foo");
done();
}, 20);
});
it('resolve value is propagated to the next entry', function (done) {
var queue = new RSVP.Queue(),
pushed_result;
queue.push(function () {
return "foo";
});
queue.push(function (value) {
pushed_result = value;
return "bar";
});
setTimeout(function() {
assert.equal(pushed_result, "foo");
assert.equal(queue.isFulfilled, true);
assert.equal(queue.fulfillmentValue, "bar");
done();
}, 20);
});
it('should be rejected if one entry is rejected', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return RSVP.timeout(30);
});
setTimeout(function() {
assert.equal(queue.isRejected, undefined);
setTimeout(function() {
assert.equal(queue.isRejected, true);
done();
}, 20);
}, 20);
});
it('get the rejectedReason from the rejected entry', function (done) {
var queue = new RSVP.Queue();
queue.push(function () {
return RSVP.timeout(1);
});
setTimeout(function() {
assert.equal(queue.isRejected, true);
assert.equal(queue.rejectedReason, "Timed out after 1 ms");
done();
}, 20);
});
it('reject cancels the remaining promise', function (done) {
var queue = new RSVP.Queue(),
pushed_result;
queue.push(function () {
return RSVP.timeout(1);
});
queue.push(undefined, function (value) {
pushed_result = value;
return "bar";
});
setTimeout(function() {
assert.equal(pushed_result, "Timed out after 1 ms");
assert.equal(queue.isRejected, true);
assert.equal(queue.rejectedReason, "Timed out after 1 ms");
done();
}, 20);
});
it('cancel also cancels the remaining promise', function (done) {
var queue = new RSVP.Queue(),
pushed_result;
queue.push(function () {
return RSVP.timeout(1);
});
queue.push(undefined, function (value) {
pushed_result = value;
return "bar";
});
queue.cancel();
setTimeout(function() {
assert(pushed_result instanceof RSVP.CancellationError);
assert.equal(queue.isRejected, true);
assert(queue.rejectedReason instanceof RSVP.CancellationError);
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