diff --git a/lib/rsvp.js b/lib/rsvp.js index 4c06b20d792980d983328430449a524be41dd33b..807c1a6aeeabe5fc5f96a4f8aad214713c93e7cd 100644 --- a/lib/rsvp.js +++ b/lib/rsvp.js @@ -3,6 +3,7 @@ import { CancellationError } from "./rsvp/cancellation_error"; import { Promise } from "./rsvp/promise"; import { denodeify } from "./rsvp/node"; import { all, any } from "./rsvp/all"; +import { Queue, ResolvedQueueError } from "./rsvp/queue"; import { delay, timeout } from "./rsvp/timeout"; import { hash } from "./rsvp/hash"; import { rethrow } from "./rsvp/rethrow"; @@ -15,4 +16,4 @@ function configure(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 }; diff --git a/lib/rsvp/queue.js b/lib/rsvp/queue.js new file mode 100644 index 0000000000000000000000000000000000000000..62709cf18847a896b1cdd308f3b810733b1c0704 --- /dev/null +++ b/lib/rsvp/queue.js @@ -0,0 +1,75 @@ +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 }; diff --git a/test/tests/extension_test.js b/test/tests/extension_test.js index 700d4e280d29511d7f5f27c314bf9c9d3d218caa..96c09e2c0f8c0bd5d267281d1bb148b87b71bb37 100644 --- a/test/tests/extension_test.js +++ b/test/tests/extension_test.js @@ -831,7 +831,6 @@ describe("RSVP extensions", function() { specify('resolves an empty array passed to RSVP.any()', function(done) { RSVP.any([]).then(function(result) { - console.log(result); assert.equal(result, undefined); done(); }); @@ -1705,3 +1704,265 @@ describe("`RSVP.timeout`", function () { }, 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); + }); + }); +});