Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit 581e4941c5437d31db92066f4bd3684c0b468a28 @mbostock mbostock committed Jan 28, 2012
Showing with 291 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +26 −0 LICENSE
  3. +19 −0 Makefile
  4. +28 −0 README.md
  5. +26 −0 package.json
  6. +70 −0 queue.js
  7. +1 −0 queue.min.js
  8. +119 −0 test/queue-test.js
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules
26 LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,19 @@
+# See the README for installation instructions.
+
+NODE_PATH ?= ./node_modules
+JS_COMPILER = $(NODE_PATH)/uglify-js/bin/uglifyjs
+JS_TESTER = $(NODE_PATH)/vows/bin/vows
+
+JS_FILES = \
+ queue.js
+
+all: \
+ $(JS_FILES) \
+ $(JS_FILES:.js=.min.js)
+
+test: all
+ @$(JS_TESTER)
+
+%.min.js: %.js Makefile
+ @rm -f $@
+ $(JS_COMPILER) < $< > $@
@@ -0,0 +1,28 @@
+# queue.js
+
+ **Queue.js** is yet another asynchronous helper library for JavaScript. Think of it as a minimalist version of [Async.js](https://github.com/caolan/async) that allows fine-tuning over parallelism. Queue.js does not use code generation.
+
+For example:
+
+```js
+queue()
+ .defer(fs.stat, __dirname + "/../Makefile")
+ .defer(fs.stat, __dirname + "/../package.json")
+ .await(function(error, results) { console.log(results); });
+```
+
+## API Reference
+
+### queue([parallelism])
+
+Constructs a new queue with the specified *parallelism*. If *parallelism* is not specified, the queue has infinite parallelism. Otherwise, *parallelism* is a positive integer.
+
+For example, if *parallelism* is 1, then all tasks will be run in series. If *parallelism* is 3, then at most three tasks will be allowed to proceed concurrently; this is useful, for example, when loading resources in a web browser.
+
+### queue.defer(method[, arguments…])
+
+Adds the specified *method* to the queue, with any optional *arguments*.
+
+### queue.await(callback)
+
+Sets the *callback* to be notified when all deferred tasks have finished.
@@ -0,0 +1,26 @@
+{
+ "name": "queue",
+ "version": "0.0.1",
+ "description": "A little helper for asynchronous JavaScript.",
+ "keywords": [
+ "asynchronous",
+ "async",
+ "queue"
+ ],
+ "author": {
+ "name": "Mike Bostock",
+ "url": "http://bost.ocks.org/mike"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/mbostock/queue.git"
+ },
+ "main": "queue.js",
+ "devDependencies": {
+ "uglify-js": "1.2.3",
+ "vows": "0.6.x"
+ },
+ "scripts": {
+ "test": "./node_modules/vows/bin/vows"
+ }
+}
@@ -0,0 +1,70 @@
+(function() {
+ if (typeof module === "undefined") self.queue = queue;
+ else module.exports = queue;
+
+ var slice = Array.prototype.slice;
+
+ queue.version = "0.0.1";
+
+ function queue(parallelism) {
+ var queue = {},
+ active = 0, // number of in-flight deferrals
+ remaining = 0, // number of deferrals remaining
+ index = -1, // monotonically-increasing index
+ head, tail, // singly-linked list of deferrals
+ error = null,
+ results = [],
+ await = remember;
+
+ if (!parallelism) parallelism = Infinity;
+
+ queue.defer = function() {
+ var node = {index: ++index, args: arguments, next: null};
+ if (tail) tail.next = node, tail = tail.next;
+ else head = tail = node;
+ ++remaining;
+ pop();
+ return queue;
+ };
+
+ queue.await = function(func) {
+ await = func;
+ if (!remaining) await(error, results);
+ return queue;
+ };
+
+ function remember(e, r) {
+ error = e;
+ results = r;
+ }
+
+ function pop() {
+ if (head && active < parallelism) {
+ var node = head,
+ func = node.args[0],
+ args = slice.call(node.args, 1),
+ index = node.index;
+ if (head == tail) head = tail = null;
+ else head = head.next;
+ ++active;
+ args.push(function(error, result) {
+ --active;
+ if (error) {
+ if (remaining) {
+ remaining = 0; // don't callback again
+ head = tail = null; // cancel other queued tasks
+ await(error, null); // callback with the error
+ }
+ } else {
+ results[index] = result;
+ if (!--remaining) await(null, results);
+ else pop();
+ }
+ });
+ func.apply(null, args);
+ }
+ }
+
+ return queue;
+ }
+})();
Oops, something went wrong.
@@ -0,0 +1,119 @@
+var queue = require("../queue"),
+ fs = require("fs"),
+ vows = require("vows"),
+ assert = require("assert");
+
+var suite = vows.describe("queue");
+
+suite.addBatch({
+
+ "version": {
+ "is semantic": function() {
+ assert.isTrue(/^([0-9]+)\.([0-9]+)\.([0-9]+)/.test(queue.version));
+ }
+ },
+
+ "queue of fs.stat": {
+ topic: function() {
+ queue()
+ .defer(fs.stat, __dirname + "/../Makefile")
+ .defer(fs.stat, __dirname + "/../README.md")
+ .defer(fs.stat, __dirname + "/../package.json")
+ .await(this.callback);
+ },
+ "does not fail": function(error, results) {
+ assert.isNull(error);
+ },
+ "successfully executes the three tasks": function(error, results) {
+ assert.greater(results[0].size, 0);
+ assert.greater(results[1].size, 0);
+ assert.greater(results[2].size, 0);
+ assert.equal(results.length, 3);
+ }
+ },
+
+ "fully-parallel queue of ten tasks": {
+ topic: function() {
+ var t = task();
+ queue()
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .await(this.callback);
+ },
+ "does not fail": function(error, results) {
+ assert.isNull(error);
+ },
+ "executes all tasks in parallel": function(error, results) {
+ assert.deepEqual(results, [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]);
+ }
+ },
+
+ "partly-parallel queue of ten tasks": {
+ topic: function() {
+ var t = task();
+ queue(3)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .await(this.callback);
+ },
+ "does not fail": function(error, results) {
+ assert.isNull(error);
+ },
+ "executes all tasks in parallel": function(error, results) {
+ assert.deepEqual(results, [3, 3, 3, 3, 3, 3, 3, 3, 2, 1]);
+ }
+ },
+
+ "serialized queue of ten tasks": {
+ topic: function() {
+ var t = task();
+ queue(1)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .defer(t)
+ .await(this.callback);
+ },
+ "does not fail": function(error, results) {
+ assert.isNull(error);
+ },
+ "executes all tasks in parallel": function(error, results) {
+ assert.deepEqual(results, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+ }
+ }
+
+});
+
+suite.export(module);
+
+function task() {
+ var active = 0;
+ return function(callback) {
+ ++active;
+ process.nextTick(function() {
+ callback(null, active--);
+ });
+ };
+}

0 comments on commit 581e494

Please sign in to comment.