[Pkg-javascript-commits] [node-seq] 03/09: Imported Upstream version 0.3.5
Ross Gammon
ross-guest at moszumanska.debian.org
Sun Mar 13 16:12:14 UTC 2016
This is an automated email from the git hooks/post-receive script.
ross-guest pushed a commit to branch master
in repository node-seq.
commit 8178eee3e2b2723526255193fb13b237259a6975
Author: Ross Gammon <rossgammon at mail.dk>
Date: Sun Mar 13 11:22:15 2016 +0100
Imported Upstream version 0.3.5
---
.npmignore | 1 +
README.markdown | 442 ++++++++++++++++++++++
examples/join.js | 18 +
examples/parseq.coffee | 12 +
examples/parseq.js | 19 +
examples/stat_all.coffee | 16 +
examples/stat_all.js | 17 +
index.js | 520 ++++++++++++++++++++++++++
package.json | 33 ++
test/readdir.js | 35 ++
test/seq.js | 946 +++++++++++++++++++++++++++++++++++++++++++++++
test/seq_.js | 149 ++++++++
12 files changed, 2208 insertions(+)
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+node_modules
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..3689261
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,442 @@
+Seq
+===
+
+Seq is an asynchronous flow control library with a chainable interface for
+sequential and parallel actions. Even the error handling is chainable.
+
+Each action in the chain operates on a stack of values.
+There is also a variables hash for storing values by name.
+
+[TOC]
+
+
+
+Examples
+========
+
+stat_all.js
+-----------
+
+````javascript
+var fs = require('fs');
+var Hash = require('hashish');
+var Seq = require('seq');
+
+Seq()
+ .seq(function () {
+ fs.readdir(__dirname, this);
+ })
+ .flatten()
+ .parEach(function (file) {
+ fs.stat(__dirname + '/' + file, this.into(file));
+ })
+ .seq(function () {
+ var sizes = Hash.map(this.vars, function (s) { return s.size })
+ console.dir(sizes);
+ })
+;
+````
+
+Output:
+
+ { 'stat_all.js': 404, 'parseq.js': 464 }
+
+parseq.js
+---------
+
+````javascript
+var fs = require('fs');
+var exec = require('child_process').exec;
+
+var Seq = require('seq');
+Seq()
+ .seq(function () {
+ exec('whoami', this)
+ })
+ .par(function (who) {
+ exec('groups ' + who, this);
+ })
+ .par(function (who) {
+ fs.readFile(__filename, 'ascii', this);
+ })
+ .seq(function (groups, src) {
+ console.log('Groups: ' + groups.trim());
+ console.log('This file has ' + src.length + ' bytes');
+ })
+;
+````
+
+Output:
+
+ Groups: substack : substack dialout cdrom floppy audio src video plugdev games netdev fuse www
+ This file has 464 bytes
+
+
+
+
+API
+===
+
+Each method executes callbacks with a context (its `this`) described in the next
+section. Every method returns `this`.
+
+Whenever `this()` is called with a non-falsy first argument, the error value
+propagates down to the first `catch` it sees, skipping over all actions in
+between. There is an implicit `catch` at the end of all chains that prints the
+error stack if available and otherwise just prints the error.
+
+
+
+Seq(xs=[])
+----------
+
+The constructor function creates a new `Seq` chain with the methods described
+below. The optional array argument becomes the new context stack.
+
+Array argument is new in 0.3. `Seq()` now behaves like `Seq.ap()`.
+
+
+.seq(cb)
+--------
+.seq(key, cb, *args)
+--------------------
+
+This eponymous function executes actions sequentially.
+Once all running parallel actions are finished executing,
+the supplied callback is `apply()`'d with the context stack.
+
+To execute the next action in the chain, call `this()`. The first
+argument must be the error value. The rest of the values will become the stack
+for the next action in the chain and are also available at `this.args`.
+
+If `key` is specified, the second argument sent to `this` goes to
+`this.vars[key]` in addition to the stack and `this.args`.
+`this.vars` persists across all requests unless it is overwritten.
+
+All arguments after `cb` will be bound to `cb`, which is useful because
+`.bind()` makes you set `this`. If you pass in `Seq` in the arguments list,
+it'll get transformed into `this` so that you can do:
+
+````javascript
+Seq()
+ .seq(fs.readdir, __dirname, Seq)
+ .seq(function (files) { console.dir(files) })
+;
+````
+
+which prints an array of files in `__dirname`.
+
+
+.par(cb)
+--------
+.par(key, cb, *args)
+--------------------
+
+Use `par` to execute actions in parallel.
+Chain multiple parallel actions together and collect all the responses on the
+stack with a sequential operation like `seq`.
+
+Each `par` sets one element in the stack with the second argument to `this()` in
+the order in which it appears, so multiple `par`s can be chained together.
+
+Like with `seq`, the first argument to `this()` should be the error value and
+the second will get pushed to the stack. Further arguments are available in
+`this.args`.
+
+If `key` is specified, the result from the second argument send to `this()` goes
+to `this.vars[key]`.
+`this.vars` persists across all requests unless it is overwritten.
+
+All arguments after `cb` will be bound to `cb`, which is useful because
+`.bind()` makes you set `this`. Like `.seq()`, you can pass along `Seq` in these
+bound arguments and it will get tranformed into `this`.
+
+
+.catch(cb)
+----------
+
+Catch errors. Whenever a function calls `this` with a non-falsy first argument,
+the message propagates down the chain to the first `catch` it sees.
+The callback `cb` fires with the error object as its first argument and the key
+that the action that caused the error was populating, which may be undefined.
+
+`catch` is a sequential action and further actions may appear after a `catch` in
+a chain. If the execution reaches a `catch` in a chain and no error has occured,
+the `catch` is skipped over.
+
+For convenience, there is a default error handler at the end of all chains.
+This default error handler looks like this:
+
+````javascript
+.catch(function (err) {
+ console.error(err.stack ? err.stack : err)
+})
+````
+
+
+.forEach(cb)
+------------
+
+Execute each action in the stack under the context of the chain object.
+`forEach` does not wait for any of the actions to finish and does not itself
+alter the stack, but the callback may alter the stack itself by modifying
+`this.stack`.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index.
+
+`forEach` is a sequential operation like `seq` and won't run until all pending
+parallel requests yield results.
+
+
+.seqEach(cb)
+------------
+
+Like `forEach`, call `cb` for each element on the stack, but unlike `forEach`,
+`seqEach` waits for the callback to yield with `this` before moving on to the
+next element in the stack.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index.
+
+If `this()` is supplied non-falsy error, the error propagates downward but any
+other arguments are ignored. `seqEach` does not modify the stack itself.
+
+
+.parEach(cb)
+------------
+.parEach(limit, cb)
+-------------------
+
+Like `forEach`, calls cb for each element in the stack and doesn't wait for the
+callback to yield a result with `this()` before moving on to the next iteration.
+Unlike `forEach`, `parEach` waits for all actions to call `this()` before moving
+along to the next action in the chain.
+
+The callback is executed `cb(x,i)` where `x` is the element and `i` is the
+index.
+
+`parEach` does not modify the stack itself and errors supplied to `this()`
+propagate.
+
+Optionally, if limit is supplied to `parEach`, at most `limit` callbacks will be
+active at a time.
+
+
+.seqMap(cb)
+-----------
+
+Like `seqEach`, but collect the values supplied to `this` and set the stack to
+these values.
+
+
+.parMap(cb)
+-----------
+.parMap(limit, cb)
+------------------
+
+Like `parEach`, but collect the values supplied to `this` and set the stack to
+these values.
+
+
+.seqFilter(cb)
+-----------
+
+Executes the callback `cb(x, idx)` against each element on the stack, waiting for the
+callback to yield with `this` before moving on to the next element. If the callback
+returns an error or a falsey value, the element will not be included in the resulting
+stack.
+
+Any errors from the callback are consumed and **do not** propagate.
+
+Calls to `this.into(i)` will place the value, if accepted by the callback, at the index in
+the results as if it were ordered at i-th index on the stack before filtering (with ties
+broken by the values). This implies `this.into` will never override another stack value
+even if their indices collide. Finally, the value will only actually appear at `i` if the
+callback accepts or moves enough values before `i`.
+
+
+.parFilter(cb)
+-----------
+.parFilter(limit, cb)
+------------------
+
+Executes the callback `cb(x, idx)` against each element on the stack, but **does not**
+wait for it to yield before moving on to the next element. If the callback returns an
+error or a falsey value, the element will not be included in the resulting stack.
+
+Any errors from the callback are consumed and **do not** propagate.
+
+Calls to `this.into(i)` will place the value, if accepted by the callback, at the index in
+the results as if it were ordered at i-th index on the stack before filtering (with ties
+broken by the values). This implies `this.into` will never override another stack value
+even if their indices collide. Finally, the value will only actually appear at `i` if the
+callback accepts or moves enough values before `i`.
+
+Optionally, if limit is supplied to `parEach`, at most `limit` callbacks will be
+active at a time.
+
+
+.do(cb)
+-------
+Create a new nested context. `cb`'s first argument is the previous context, and `this`
+is the nested `Seq` object.
+
+
+.flatten(fully=true)
+--------------------
+
+Recursively flatten all the arrays in the stack. Set `fully=false` to flatten
+only one level.
+
+
+.unflatten()
+------------
+
+Turn the contents of the stack into a single array item. You can think of it
+as the inverse of `flatten(false)`.
+
+
+.extend([x,y...])
+-----------------
+
+Like `push`, but takes an array. This is like python's `[].extend()`.
+
+
+.set(xs)
+--------
+
+Set the stack to a new array. This assigns the reference, it does not copy.
+
+
+.empty()
+--------
+
+Set the stack to [].
+
+
+.push(x,y...), .pop(), .shift(), .unshift(x), .splice(...), reverse()
+---------------------------------------------------------------------
+.map(...), .filter(...), .reduce(...)
+-------------------------------------
+
+Executes an array operation on the stack.
+
+The methods `map`, `filter`, and `reduce` are also proxies to their Array counterparts:
+they have identical signatures to the Array methods, operate synchronously on the context
+stack, and do not pass a Context object (unlike `seqMap` and `parMap`).
+
+The result of the transformation is assigned to the context stack; in the case of `reduce`,
+if you do not return an array, the value will be wrapped in one.
+
+````javascript
+Seq([1, 2, 3])
+ .reduce(function(sum, x){ return sum + x; }, 0)
+ .seq(function(sum){
+ console.log('sum: %s', sum);
+ // sum: 6
+ console.log('stack is Array?', Array.isArray(this.stack));
+ // stack is Array: true
+ console.log('stack:', this.stack);
+ // stack: [6]
+ })
+;
+````
+
+
+
+
+Explicit Parameters
+-------------------
+
+For environments like coffee-script or nested logic where threading `this` is
+bothersome, you can use:
+
+* seq_
+* par_
+* forEach_
+* seqEach_
+* parEach_
+* seqMap_
+* parMap_
+
+which work exactly like their un-underscored counterparts except for the first
+parameter to the supplied callback is set to the context, `this`.
+
+
+
+Context Object
+==============
+
+Each callback gets executed with its `this` set to a function in order to yield
+results, error values, and control. The function also has these useful fields:
+
+this.stack
+----------
+
+The execution stack.
+
+this.stack_
+-----------
+
+The previous stack value, mostly used internally for hackish purposes.
+
+this.vars
+---------
+
+A hash of key/values populated with `par(key, ...)`, `seq(key, ...)` and
+`this.into(key)`.
+
+this.into(key)
+--------------
+
+Instead of sending values to the stack, sets a key and returns `this`.
+Use `this.into(key)` interchangeably with `this` for yielding keyed results.
+`into` overrides the optional key set by `par(key, ...)` and `seq(key, ...)`.
+
+this.ok
+-------
+
+Set the `err` to null. Equivalent to `this.bind(this, null)`.
+
+this.args
+---------
+
+`this.args` is like `this.stack`, but it contains all the arguments to `this()`
+past the error value, not just the first. `this.args` is an array with the same
+indices as `this.stack` but also stores keyed values for the last sequential
+operation. Each element in `this.array` is set to `[].slice.call(arguments, 1)`
+from inside `this()`.
+
+this.error
+----------
+
+This is used for error propagation. You probably shouldn't mess with it.
+
+
+
+Installation
+============
+
+With [npm](http://github.com/isaacs/npm), just do:
+
+ npm install seq
+
+or clone this project on github:
+
+ git clone http://github.com/substack/node-seq.git
+
+To run the tests with [expresso](http://github.com/visionmedia/expresso),
+just do:
+
+ expresso
+
+
+
+Dependencies
+------------
+
+This module uses [chainsaw](http://github.com/substack/node-chainsaw)
+When you `npm install seq` this dependency will automatically be installed.
+
+
diff --git a/examples/join.js b/examples/join.js
new file mode 100644
index 0000000..cc05c4f
--- /dev/null
+++ b/examples/join.js
@@ -0,0 +1,18 @@
+var Seq = require('seq');
+Seq()
+ .par(function () {
+ var that = this;
+ setTimeout(function () { that(null, 'a') }, 300);
+ })
+ .par(function () {
+ var that = this;
+ setTimeout(function () { that(null, 'b') }, 200);
+ })
+ .par(function () {
+ var that = this;
+ setTimeout(function () { that(null, 'c') }, 100);
+ })
+ .seq(function (a, b, c) {
+ console.dir([ a, b, c ])
+ })
+;
diff --git a/examples/parseq.coffee b/examples/parseq.coffee
new file mode 100644
index 0000000..d4ca0ab
--- /dev/null
+++ b/examples/parseq.coffee
@@ -0,0 +1,12 @@
+fs = require 'fs'
+exec = require('child_process').exec
+Seq = require 'seq'
+
+Seq()
+ .seq_((next) -> exec 'whoami', next)
+ .par_((next, who) -> exec('groups ' + who, next))
+ .par_((next, who) -> fs.readFile(__filename, 'utf8', next))
+ .seq_((next, groups, src) ->
+ console.log('Groups: ' + groups.trim())
+ console.log('This file has ' + src.length + ' bytes')
+ )
diff --git a/examples/parseq.js b/examples/parseq.js
new file mode 100644
index 0000000..2cdcf21
--- /dev/null
+++ b/examples/parseq.js
@@ -0,0 +1,19 @@
+var fs = require('fs');
+var exec = require('child_process').exec;
+
+var Seq = require('seq');
+Seq()
+ .seq(function () {
+ exec('whoami', this)
+ })
+ .par(function (who) {
+ exec('groups ' + who, this);
+ })
+ .par(function (who) {
+ fs.readFile(__filename, 'utf8', this);
+ })
+ .seq(function (groups, src) {
+ console.log('Groups: ' + groups.trim());
+ console.log('This file has ' + src.length + ' bytes');
+ })
+;
diff --git a/examples/stat_all.coffee b/examples/stat_all.coffee
new file mode 100644
index 0000000..83ec0b2
--- /dev/null
+++ b/examples/stat_all.coffee
@@ -0,0 +1,16 @@
+fs = require 'fs'
+Hash = require 'hashish'
+Seq = require 'seq'
+
+Seq()
+ .seq_((next) ->
+ fs.readdir(__dirname, next)
+ )
+ .flatten()
+ .parEach_((next, file) ->
+ fs.stat(__dirname + '/' + file, next.into(file))
+ )
+ .seq_((next) ->
+ sizes = Hash.map(next.vars, (s) -> s.size)
+ console.dir sizes
+ )
diff --git a/examples/stat_all.js b/examples/stat_all.js
new file mode 100644
index 0000000..b9962eb
--- /dev/null
+++ b/examples/stat_all.js
@@ -0,0 +1,17 @@
+var fs = require('fs');
+var Hash = require('hashish');
+var Seq = require('seq');
+
+Seq()
+ .seq(function () {
+ fs.readdir(__dirname, this);
+ })
+ .flatten()
+ .parEach(function (file) {
+ fs.stat(__dirname + '/' + file, this.into(file));
+ })
+ .seq(function () {
+ var sizes = Hash.map(this.vars, function (s) { return s.size })
+ console.dir(sizes);
+ })
+;
diff --git a/index.js b/index.js
new file mode 100755
index 0000000..4e0888b
--- /dev/null
+++ b/index.js
@@ -0,0 +1,520 @@
+var EventEmitter = require('events').EventEmitter;
+var Hash = require('hashish');
+var Chainsaw = require('chainsaw');
+
+module.exports = Seq;
+function Seq (xs) {
+ if (xs && !Array.isArray(xs) || arguments.length > 1) {
+ throw new Error('Optional argument to Seq() is exactly one Array');
+ }
+
+ var ch = Chainsaw(function (saw) {
+ builder.call(this, saw, xs || []);
+ });
+
+ process.nextTick(function () {
+ ch['catch'](function (err) {
+ console.error(err.stack ? err.stack : err)
+ });
+ });
+ return ch;
+}
+
+Seq.ap = Seq; // for compatability with versions <0.3
+
+function builder (saw, xs) {
+ var context = {
+ vars : {},
+ args : {},
+ stack : xs,
+ error : null
+ };
+ context.stack_ = context.stack;
+
+ function action (step, key, f, g) {
+ var cb = function (err) {
+ var args = [].slice.call(arguments, 1);
+ if (err) {
+ context.error = { message : err, key : key };
+ saw.jump(lastPar);
+ saw.down('catch');
+ g();
+ }
+ else {
+ if (typeof key == 'number') {
+ context.stack_[key] = args[0];
+ context.args[key] = args;
+ }
+ else {
+ context.stack_.push.apply(context.stack_, args);
+ if (key !== undefined) {
+ context.vars[key] = args[0];
+ context.args[key] = args;
+ }
+ }
+ if (g) g(args, key);
+ }
+ };
+ Hash(context).forEach(function (v,k) { cb[k] = v });
+
+ cb.into = function (k) {
+ key = k;
+ return cb;
+ };
+
+ cb.next = function (err, xs) {
+ context.stack_.push.apply(context.stack_, xs);
+ cb.apply(cb, [err].concat(context.stack));
+ };
+
+ cb.pass = function (err) {
+ cb.apply(cb, [err].concat(context.stack));
+ };
+
+ cb.ok = cb.bind(cb, null);
+
+ f.apply(cb, context.stack);
+ }
+
+ var running = 0;
+ var errors = 0;
+
+ this.seq = function (key, cb) {
+ var bound = [].slice.call(arguments, 2);
+
+ if (typeof key === 'function') {
+ if (arguments.length > 1) bound.unshift(cb);
+ cb = key;
+ key = undefined;
+ }
+
+ if (context.error) saw.next()
+ else if (running === 0) {
+ action(saw.step, key,
+ function () {
+ context.stack_ = [];
+ var args = [].slice.call(arguments);
+ args.unshift.apply(args, bound.map(function (arg) {
+ return arg === Seq ? this : arg
+ }, this));
+
+ cb.apply(this, args);
+ }, function () {
+ context.stack = context.stack_;
+ saw.next()
+ }
+ );
+ }
+ };
+
+ var lastPar = null;
+ this.par = function (key, cb) {
+ lastPar = saw.step;
+
+ if (running == 0) {
+ // empty the active stack for the first par() in a chain
+ context.stack_ = [];
+ }
+
+ var bound = [].slice.call(arguments, 2);
+ if (typeof key === 'function') {
+ if (arguments.length > 1) bound.unshift(cb);
+ cb = key;
+ key = context.stack_.length;
+ context.stack_.push(null);
+ }
+ var cb_ = function () {
+ var args = [].slice.call(arguments);
+ args.unshift.apply(args, bound.map(function (arg) {
+ return arg === Seq ? this : arg
+ }, this));
+
+ cb.apply(this, args);
+ };
+
+ running ++;
+
+ var step = saw.step;
+ process.nextTick(function () {
+ action(step, key, cb_, function (args) {
+ if (!args) errors ++;
+
+ running --;
+ if (running == 0) {
+ context.stack = context.stack_.slice();
+ saw.step = lastPar;
+ if (errors > 0) saw.down('catch');
+ errors = 0;
+ saw.next();
+ }
+ });
+ });
+ saw.next();
+ };
+
+ [ 'seq', 'par' ].forEach(function (name) {
+ this[name + '_'] = function (key) {
+ var args = [].slice.call(arguments);
+
+ var cb = typeof key === 'function'
+ ? args[0] : args[1];
+
+ var fn = function () {
+ var argv = [].slice.call(arguments);
+ argv.unshift(this);
+ cb.apply(this, argv);
+ };
+
+ if (typeof key === 'function') {
+ args[0] = fn;
+ }
+ else {
+ args[1] = fn;
+ }
+
+ this[name].apply(this, args);
+ };
+ }, this);
+
+ this['catch'] = function (cb) {
+ if (context.error) {
+ cb.call(context, context.error.message, context.error.key);
+ context.error = null;
+ }
+ saw.next();
+ };
+
+ this.forEach = function (cb) {
+ this.seq(function () {
+ context.stack_ = context.stack.slice();
+ var end = context.stack.length;
+
+ if (end === 0) this(null)
+ else context.stack.forEach(function (x, i) {
+ action(saw.step, i, function () {
+ cb.call(this, x, i);
+ if (i == end - 1) saw.next();
+ });
+ });
+ });
+ };
+
+ this.seqEach = function (cb) {
+ this.seq(function () {
+ context.stack_ = context.stack.slice();
+ var xs = context.stack.slice();
+ if (xs.length === 0) this(null);
+ else (function next (i) {
+ action(
+ saw.step, i,
+ function () { cb.call(this, xs[i], i) },
+ function (args) {
+ if (!args || i === xs.length - 1) saw.next();
+ else next(i + 1);
+ }
+ );
+ }).bind(this)(0);
+ });
+ };
+
+ this.parEach = function (limit, cb) {
+ var xs = context.stack.slice();
+ if (cb === undefined) { cb = limit; limit = xs.length }
+ context.stack_ = [];
+
+ var active = 0;
+ var finished = 0;
+ var queue = [];
+
+ if (xs.length === 0) saw.next()
+ else xs.forEach(function call (x, i) {
+ if (active >= limit) {
+ queue.push(call.bind(this, x, i));
+ }
+ else {
+ active ++;
+ action(saw.step, i,
+ function () {
+ cb.call(this, x, i);
+ },
+ function () {
+ active --;
+ finished ++;
+ if (queue.length > 0) queue.shift()();
+ else if (finished === xs.length) {
+ saw.next();
+ }
+ }
+ );
+ }
+ });
+ };
+
+ this.parMap = function (limit, cb) {
+ var res = [];
+ var len = context.stack.length;
+ if (cb === undefined) { cb = limit; limit = len }
+ var res = [];
+
+ Seq()
+ .extend(context.stack)
+ .parEach(limit, function (x, i) {
+ var self = this;
+
+ var next = function () {
+ res[i] = arguments[1];
+ self.apply(self, arguments);
+ };
+
+ next.stack = self.stack;
+ next.stack_ = self.stack_;
+ next.vars = self.vars;
+ next.args = self.args;
+ next.error = self.error;
+
+ next.into = function (key) {
+ return function () {
+ res[key] = arguments[1];
+ self.apply(self, arguments);
+ };
+ };
+
+ next.ok = function () {
+ var args = [].slice.call(arguments);
+ args.unshift(null);
+ return next.apply(next, args);
+ };
+
+ cb.apply(next, arguments);
+ })
+ .seq(function () {
+ context.stack = res;
+ saw.next();
+ })
+ ;
+ };
+
+ this.seqMap = function (cb) {
+ var res = [];
+ var lastIdx = context.stack.length - 1;
+
+ this.seqEach(function (x, i) {
+ var self = this;
+
+ var next = function () {
+ res[i] = arguments[1];
+ if (i === lastIdx)
+ context.stack = res;
+ self.apply(self, arguments);
+ };
+
+ next.stack = self.stack;
+ next.stack_ = self.stack_;
+ next.vars = self.vars;
+ next.args = self.args;
+ next.error = self.error;
+
+ next.into = function (key) {
+ return function () {
+ res[key] = arguments[1];
+ if (i === lastIdx)
+ context.stack = res;
+ self.apply(self, arguments);
+ };
+ };
+
+ next.ok = function () {
+ var args = [].slice.call(arguments);
+ args.unshift(null);
+ return next.apply(next, args);
+ };
+
+ cb.apply(next, arguments);
+ });
+ };
+
+ /**
+ * Consumes any errors that occur in `cb`. Calls to `this.into(i)` will place
+ * that value, if accepted by the filter, at the index in the results as
+ * if it were the i-th index before filtering. (This means it will never
+ * override another value, and will only actually appear at i if the filter
+ * accepts all values before i.)
+ */
+ this.parFilter = function (limit, cb) {
+ var res = [];
+ var len = context.stack.length;
+ if (cb === undefined) { cb = limit; limit = len }
+ var res = [];
+
+ Seq()
+ .extend(context.stack)
+ .parEach(limit, function (x, i) {
+ var self = this;
+
+ var next = function (err, ok) {
+ if (!err && ok)
+ res.push([i, x]);
+ arguments[0] = null; // discard errors
+ self.apply(self, arguments);
+ };
+
+ next.stack = self.stack;
+ next.stack_ = self.stack_;
+ next.vars = self.vars;
+ next.args = self.args;
+ next.error = self.error;
+
+ next.into = function (key) {
+ return function (err, ok) {
+ if (!err && ok)
+ res.push([key, x]);
+ arguments[0] = null; // discard errors
+ self.apply(self, arguments);
+ };
+ };
+
+ next.ok = function () {
+ var args = [].slice.call(arguments);
+ args.unshift(null);
+ return next.apply(next, args);
+ };
+
+ cb.apply(next, arguments);
+ })
+ .seq(function () {
+ context.stack = res.sort().map(function(pair){ return pair[1]; });
+ saw.next();
+ })
+ ;
+ };
+
+ /**
+ * Consumes any errors that occur in `cb`. Calls to `this.into(i)` will place
+ * that value, if accepted by the filter, at the index in the results as
+ * if it were the i-th index before filtering. (This means it will never
+ * override another value, and will only actually appear at i if the filter
+ * accepts all values before i.)
+ */
+ this.seqFilter = function (cb) {
+ var res = [];
+ var lastIdx = context.stack.length - 1;
+
+ this.seqEach(function (x, i) {
+ var self = this;
+
+ var next = function (err, ok) {
+ if (!err && ok)
+ res.push([i, x]);
+ if (i === lastIdx)
+ context.stack = res.sort().map(function(pair){ return pair[1]; });
+ arguments[0] = null; // discard errors
+ self.apply(self, arguments);
+ };
+
+ next.stack = self.stack;
+ next.stack_ = self.stack_;
+ next.vars = self.vars;
+ next.args = self.args;
+ next.error = self.error;
+
+ next.into = function (key) {
+ return function (err, ok) {
+ if (!err && ok)
+ res.push([key, x]);
+ if (i === lastIdx)
+ context.stack = res.sort().map(function(pair){ return pair[1]; });
+ arguments[0] = null; // discard errors
+ self.apply(self, arguments);
+ };
+ };
+
+ next.ok = function () {
+ var args = [].slice.call(arguments);
+ args.unshift(null);
+ return next.apply(next, args);
+ };
+
+ cb.apply(next, arguments);
+ });
+ };
+
+ [ 'forEach', 'seqEach', 'parEach', 'seqMap', 'parMap', 'seqFilter', 'parFilter' ]
+ .forEach(function (name) {
+ this[name + '_'] = function (cb) {
+ this[name].call(this, function () {
+ var args = [].slice.call(arguments);
+ args.unshift(this);
+ cb.apply(this, args);
+ });
+ };
+ }, this)
+ ;
+
+ ['push','pop','shift','unshift','splice','reverse']
+ .forEach(function (name) {
+ this[name] = function () {
+ context.stack[name].apply(
+ context.stack,
+ [].slice.call(arguments)
+ );
+ saw.next();
+ return this;
+ };
+ }, this)
+ ;
+
+ [ 'map', 'filter', 'reduce' ]
+ .forEach(function (name) {
+ this[name] = function () {
+ var res = context.stack[name].apply(
+ context.stack,
+ [].slice.call(arguments)
+ );
+ // stack must be an array, or bad things happen
+ context.stack = (Array.isArray(res) ? res : [res]);
+ saw.next();
+ return this;
+ };
+ }, this)
+ ;
+
+ this.extend = function (xs) {
+ if (!Array.isArray(xs)) {
+ throw new Error('argument to .extend() is not an Array');
+ }
+ context.stack.push.apply(context.stack, xs);
+ saw.next();
+ };
+
+ this.flatten = function (pancake) {
+ var xs = [];
+ // should we fully flatten this array? (default: true)
+ if (pancake === undefined) { pancake = true; }
+ context.stack.forEach(function f (x) {
+ if (Array.isArray(x) && pancake) x.forEach(f);
+ else if (Array.isArray(x)) xs = xs.concat(x);
+ else xs.push(x);
+ });
+ context.stack = xs;
+ saw.next();
+ };
+
+ this.unflatten = function () {
+ context.stack = [context.stack];
+ saw.next();
+ };
+
+ this.empty = function () {
+ context.stack = [];
+ saw.next();
+ };
+
+ this.set = function (stack) {
+ context.stack = stack;
+ saw.next();
+ };
+
+ this['do'] = function (cb) {
+ saw.nest(cb, context);
+ };
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0c971d6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+ "name" : "seq",
+ "version" : "0.3.5",
+ "description" : "Chainable asynchronous flow control with sequential and parallel primitives and pipeline-style error handling",
+ "main" : "./index.js",
+ "repository" : {
+ "type" : "git",
+ "url" : "http://github.com/substack/node-seq.git"
+ },
+ "dependencies" : {
+ "chainsaw" : ">=0.0.7 <0.1",
+ "hashish" : ">=0.0.2 <0.1"
+ },
+ "devDependencies" : {
+ "expresso" : ">=0.7.x"
+ },
+ "script" : {
+ "test" : "expresso"
+ },
+ "keywords" : [
+ "flow-control", "flow", "control", "async", "asynchronous", "chain",
+ "pipeline", "sequence", "sequential", "parallel", "error"
+ ],
+ "author" : {
+ "name" : "James Halliday",
+ "email" : "mail at substack.net",
+ "url" : "http://substack.net"
+ },
+ "license" : "MIT/X11",
+ "engine" : {
+ "node" : ">=0.4.0"
+ }
+}
diff --git a/test/readdir.js b/test/readdir.js
new file mode 100644
index 0000000..fe1a38b
--- /dev/null
+++ b/test/readdir.js
@@ -0,0 +1,35 @@
+var assert = require('assert');
+var Seq = require('seq');
+var fs = require('fs');
+
+exports.readdir = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 500);
+
+ Seq()
+ .seq(fs.readdir, __dirname, Seq)
+ .seq(function (files) {
+ clearTimeout(to);
+ assert.ok(files.length >= 2);
+ })
+ .catch(assert.fail)
+ ;
+};
+
+exports.readdirs = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 500);
+
+ Seq()
+ .par(fs.readdir, __dirname, Seq)
+ .par(fs.readdir, __dirname + '/../examples', Seq)
+ .seq(function (tests, examples) {
+ clearTimeout(to);
+ assert.ok(tests.length >= 2);
+ assert.ok(examples.length >= 2);
+ })
+ .catch(assert.fail)
+ ;
+};
diff --git a/test/seq.js b/test/seq.js
new file mode 100644
index 0000000..2e34aec
--- /dev/null
+++ b/test/seq.js
@@ -0,0 +1,946 @@
+var Seq = require('seq');
+var assert = require('assert');
+
+exports.seq = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 100);
+
+ Seq([0])
+ .seq('pow', function (n) {
+ this(null, 1);
+ })
+ .seq(function (n) {
+ assert.eql(n, 1);
+ assert.eql(n, this.vars.pow);
+ var seq = this;
+ setTimeout(function () { seq(null, 2) }, 25);
+ assert.eql(this.stack, [n]);
+ })
+ .seq(function (n) {
+ assert.eql(n, 2);
+ assert.eql(this.stack, [n]);
+ this(null, 5, 6, 7);
+ })
+ .seq(function (x, y, z) {
+ clearTimeout(to);
+ assert.eql([x,y,z], [5,6,7]);
+ })
+ ;
+};
+
+exports.into = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 10);
+ var calls = 0;
+
+ Seq([3,4,5])
+ .seq(function () {
+ this.into('w')(null, 5);
+ })
+ .seq(function (w) {
+ clearTimeout(to);
+ assert.eql(w, this.vars.w);
+ assert.eql(arguments.length, 1);
+ assert.eql(w, 5);
+ })
+ ;
+};
+
+exports.catchSeq = function () {
+ var to = setTimeout(function () {
+ assert.fail('never caught the error');
+ }, 100);
+
+ var tf = setTimeout(function () {
+ assert.fail('final action never executed');
+ }, 100);
+
+ var calls = {};
+ Seq([1])
+ .seq(function (n) {
+ assert.eql(n, 1);
+ calls.before = true;
+ this('pow!');
+ calls.after = true;
+ })
+ .seq(function (n) {
+ calls.next = true;
+ assert.fail('should have skipped this');
+ })
+ .catch(function (err) {
+ assert.eql(err, 'pow!');
+ assert.ok(calls.before);
+ assert.ok(!calls.after);
+ assert.ok(!calls.next);
+ clearTimeout(to);
+ })
+ .do(function () {
+ //assert.ok(calls.after);
+ clearTimeout(tf);
+ })
+ ;
+};
+
+exports.par = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 1000);
+
+ Seq()
+ .seq(function () {
+ this(null, 'mew');
+ })
+ .par(function () {
+ var seq = this;
+ setTimeout(function () { seq(null, 'x') }, 50);
+ })
+ .par(function () {
+ var seq = this;
+ setTimeout(function () { seq(null, 'y') }, 25);
+ })
+ .par('z', function () {
+ this(null, 42);
+ })
+ .seq(function (x, y, z) {
+ clearTimeout(to);
+ assert.eql(x, 'x');
+ assert.eql(y, 'y');
+ assert.eql(z, 42);
+ assert.eql(this.args, { 0 : ['x'], 1 : ['y'], z : [42] });
+ assert.eql(this.stack, [ 'x', 'y', 42 ]);
+ assert.eql(this.vars, { z : 42 });
+ })
+ ;
+};
+
+exports.catchPar = function () {
+ var done = false, caught = false;
+ var tc = setTimeout(function () {
+ assert.fail('error not caught');
+ }, 1000);
+
+ Seq()
+ .par('one', function () {
+ setTimeout(this.bind({}, 'rawr'), 25);
+ })
+ .par('two', function () {
+ setTimeout(this.bind({}, null, 'y'), 50);
+ })
+ .seq(function (x, y) {
+ assert.fail('seq fired with error above');
+ })
+ .catch(function (err, key) {
+ clearTimeout(tc);
+ assert.eql(err, 'rawr');
+ assert.eql(key, 'one');
+ })
+ ;
+};
+
+exports.catchParWithoutSeq = function () {
+ var done = false, caught = false;
+ var tc = setTimeout(function () {
+ assert.fail('error not caught');
+ }, 5000);
+
+ Seq()
+ .par('one', function () {
+ setTimeout(this.bind({}, 'rawr'), 25);
+ })
+ .par('two', function () {
+ setTimeout(this.bind({}, null, 'y'), 50);
+ })
+ .catch(function (err, key) {
+ clearTimeout(tc);
+ assert.eql(err, 'rawr');
+ assert.eql(key, 'one');
+ })
+ ;
+}
+
+exports.catchParMultipleErrors = function() {
+ var caught={};
+ var to = setTimeout(function() {
+ assert.fail('Never finished');
+ }, 1000);
+ var times = 0;
+
+ Seq()
+ .par('one', function() {
+ setTimeout(this.bind({}, 'rawr1'), 25);
+ })
+ .par('two', function() {
+ setTimeout(this.bind({}, 'rawr2'), 50);
+ })
+ .catch(function(err,key) {
+ caught[key] = err;
+ })
+ .seq(function() {
+ clearTimeout(to);
+ times ++;
+ assert.eql(times, 1);
+ assert.eql(caught, { one:'rawr1', two:'rawr2' });
+ })
+ ;
+};
+
+exports.catchParThenSeq = function () {
+ var tc = setTimeout(function () {
+ assert.fail('error not caught');
+ }, 1000);
+ var tf = setTimeout(function () {
+ assert.fail('final seq not run');
+ }, 500);
+ var times = 0;
+ var errs = [
+ { key : 'one', msg : 'rawr' },
+ { key : 'four', msg : 'pow' },
+ ];
+
+ Seq()
+ .par('one', function () {
+ setTimeout(this.bind({}, 'rawr'), 25);
+ })
+ .par('two', function () {
+ setTimeout(this.bind({}, null, 'y'), 50);
+ })
+ .par('three', function () {
+ setTimeout(this.bind({}, null, 'z'), 30);
+ })
+ .par('four', function () {
+ setTimeout(this.bind({}, 'pow'), 45);
+ })
+ .seq(function (x, y) {
+ assert.fail('seq fired with error above');
+ })
+ .catch(function (err, key) {
+ clearTimeout(tc);
+ var e = errs.shift();
+ assert.eql(err, e.msg);
+ assert.eql(key, e.key);
+ })
+ .seq(function () {
+ clearTimeout(tf);
+ times ++;
+ assert.eql(times, 1);
+ })
+ ;
+}
+
+exports.forEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired after forEach');
+ }, 25);
+
+ var count = 0;
+ Seq([1,2,3])
+ .push(4)
+ .forEach(function (x, i) {
+ assert.eql(x - 1, i);
+ count ++;
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(count, 4);
+ })
+ ;
+};
+
+exports.seqEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('seqEach never finished');
+ }, 25);
+
+ var count = 0;
+ var ii = 0;
+ Seq([1,2,3])
+ .seqEach(function (x, i) {
+ assert.eql(i, ii++);
+ assert.eql(x, [1,2,3][i]);
+ count ++;
+ this(null);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(count, 3);
+ })
+ ;
+};
+
+exports.seqEachCatch = function () {
+ var to = setTimeout(function () {
+ assert.fail('never caught the error');
+ }, 25);
+ var tf = setTimeout(function () {
+ assert.fail('never resumed afterwards');
+ }, 25);
+
+ var meows = [];
+
+ var values = [];
+ Seq([1,2,3,4])
+ .seqEach(function (x, i) {
+ values.push([i,x]);
+ assert.eql(x - 1, i);
+ if (i >= 2) this('meow ' + i)
+ else this(null, x * 10);
+ })
+ .seq(function (xs) {
+ assert.fail('should fail before this action');
+ })
+ .catch(function (err) {
+ clearTimeout(to);
+ meows.push(err);
+ assert.eql(err, 'meow 2');
+ assert.eql(values, [[0,1],[1,2],[2,3]]);
+ })
+ .seq(function () {
+ clearTimeout(tf);
+ })
+ ;
+};
+
+exports.parEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ var values = [];
+ Seq([1,2,3,4])
+ .parEach(function (x, i) {
+ values.push([i,x]);
+ setTimeout(this.bind({}, null), 20);
+ })
+ .seq(function () {
+ assert.deepEqual(this.stack, [1,2,3,4])
+ assert.deepEqual(values, [[0,1],[1,2],[2,3],[3,4]]);
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.parEachVars = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 1000);
+ var values = [];
+
+ Seq()
+ .seq('abc', function () {
+ this(null, 'a', 'b', 'c');
+ })
+ .parEach(function (x) {
+ values.push(x);
+ setTimeout(this.bind(this, null), Math.floor(Math.random() * 50));
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(values, ['a','b','c']);
+ assert.eql(this.stack, ['a','b','c']);
+ assert.eql(this.vars.abc, 'a');
+ })
+ ;
+};
+
+exports.parEachInto = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ Seq([1,2,3,4])
+ .parEach(function (x, i) {
+ setTimeout((function () {
+ this.into('abcd'.charAt(i))(null, x);
+ }).bind(this), 20);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.deepEqual(this.stack, [1,2,3,4])
+ assert.deepEqual(this.vars, { a : 1, b : 2, c : 3, d : 4 });
+ })
+ ;
+};
+
+exports.parEachCatch = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ var values = [];
+ Seq([1,2,3,4])
+ .parEach(function (x, i) {
+ values.push([i,x]);
+ setTimeout(this.bind({}, 'zing'), 10);
+ })
+ .seq(function () {
+ assert.fail('should have errored before this point')
+ })
+ .catch(function (err) {
+ clearTimeout(to);
+ assert.eql(err, 'zing');
+ assert.deepEqual(values, [[0,1],[1,2],[2,3],[3,4]]);
+ })
+ ;
+};
+
+exports.parEachLimited = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parEach(3, function (x, i) {
+ running ++;
+
+ assert.ok(running <= 3);
+
+ values.push([i,x]);
+ setTimeout((function () {
+ running --;
+ this(null);
+ }).bind(this), 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(values,
+ [[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9],[9,10]]
+ );
+ })
+ ;
+};
+
+exports.parMap = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parMap(2, function (x, i) {
+ running ++;
+
+ assert.ok(running <= 2);
+
+ setTimeout((function () {
+ running --;
+ this(null, x * 10);
+ }).bind(this), Math.floor(Math.random() * 100));
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+ assert.eql(this.stack, [].slice.call(arguments));
+ })
+ ;
+};
+
+exports.parMapFast = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parMap(function (x, i) {
+ this(null, x * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+ assert.eql(this.stack, [].slice.call(arguments));
+ })
+ ;
+};
+
+exports.parMapInto = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parMap(function (x, i) {
+ this.into(9 - i)(null, x * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [100, 90, 80, 70, 60, 50, 40, 30, 20, 10]);
+ assert.eql(this.stack, [].slice.call(arguments));
+ })
+ ;
+};
+
+exports.seqMap = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .seqMap(function (x, i) {
+ running ++;
+
+ assert.eql(running, 1);
+
+ setTimeout((function () {
+ running --;
+ this(null, x * 10);
+ }).bind(this), 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [10,20,30,40,50,60,70,80,90,100]);
+ })
+ ;
+};
+
+
+exports.seqMapInto = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .seqMap(function (x, i) {
+ running ++;
+
+ assert.eql(running, 1);
+
+ setTimeout((function () {
+ running --;
+ this.into(9 - i)(null, x * 10);
+ }).bind(this), 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [100, 90, 80, 70, 60, 50, 40, 30, 20, 10]);
+ })
+ ;
+};
+
+exports.parFilter = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parFilter(2, function (x, i) {
+ running ++;
+
+ assert.ok(running <= 2);
+
+ setTimeout((function () {
+ running --;
+ this(null, x % 2 === 0);
+ }).bind(this), Math.floor(Math.random() * 100));
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [2,4,6,8,10]);
+ assert.eql(this.stack, [].slice.call(arguments));
+ })
+ ;
+};
+
+exports.seqFilter = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .seqFilter(function (x, i) {
+ running ++;
+
+ assert.eql(running, 1);
+
+ setTimeout((function () {
+ running --;
+ this(null, x % 2 === 0);
+ }).bind(this), 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [2,4,6,8,10]);
+ })
+ ;
+};
+
+exports.parFilterInto = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .parFilter(2, function (x, i) {
+ running ++;
+
+ assert.ok(running <= 2);
+
+ setTimeout((function () {
+ running --;
+ this.into(x % 3)(null, x % 2 === 0);
+ }).bind(this), Math.floor(Math.random() * 100));
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [ 6, 10, 4, 2, 8 ]);
+ assert.eql(this.stack, [].slice.call(arguments));
+ })
+ ;
+};
+
+exports.seqFilterInto = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 500);
+
+ var running = 0;
+ var values = [];
+ Seq([1,2,3,4,5,6,7,8,9,10])
+ .seqFilter(function (x, i) {
+ running ++;
+
+ assert.eql(running, 1);
+
+ setTimeout((function () {
+ running --;
+ this.into(x % 3)(null, x % 2 === 0);
+ }).bind(this), 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(this.stack, [ 6, 10, 4, 2, 8 ]);
+ })
+ ;
+};
+
+exports.stack = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ Seq([4,5,6])
+ .seq(function (x, y, z) {
+ assert.eql(arguments.length, 3);
+ assert.eql([x,y,z], [4,5,6]);
+ assert.eql(this.stack, [4,5,6]);
+ this(null);
+ })
+ .set([3,4])
+ .seq(function (x, y) {
+ assert.eql(arguments.length, 2);
+ assert.eql([x,y], [3,4]);
+ assert.eql(this.stack, [3,4]);
+ this(null);
+ })
+ .empty()
+ .seq(function () {
+ assert.eql(arguments.length, 0);
+ assert.eql(this.stack, []);
+ this.next(null, ['a']);
+ })
+ .extend(['b','c'])
+ .seq(function (a, b, c) {
+ assert.eql(arguments.length, 3);
+ assert.eql([a,b,c], ['a','b','c']);
+ assert.eql(this.stack, ['a','b','c']);
+ this.pass(null);
+ })
+ .pop()
+ .push('c', 'd', 'e')
+ .seq(function (a, b, c, d, e) {
+ assert.eql(arguments.length, 5);
+ assert.eql([a,b,c,d,e], ['a','b','c','d','e']);
+ assert.eql(this.stack, ['a','b','c','d','e']);
+ this.pass(null);
+ })
+ .shift()
+ .shift()
+ .seq(function (c, d, e) {
+ assert.eql(arguments.length, 3);
+ assert.eql([c,d,e], ['c','d','e']);
+ assert.eql(this.stack, ['c','d','e']);
+ this.pass(null);
+ })
+ .set([['a',['b']],['c','d',['e']]])
+ .flatten(false) // only flatten one level
+ .seq(function (a, b, c, d, e) {
+ assert.eql(arguments.length, 5);
+ assert.eql([a,b,c,d,e], ['a',['b'],'c','d',['e']]);
+ assert.eql(this.stack, ['a',['b'],'c','d',['e']]);
+ this.pass(null);
+ })
+ .set([['a','b'],['c','d',['e']]])
+ .flatten()
+ .seq(function (a, b, c, d, e) {
+ assert.eql(arguments.length, 5);
+ assert.eql([a,b,c,d,e], ['a','b','c','d','e']);
+ assert.eql(this.stack, ['a','b','c','d','e']);
+ this.pass(null);
+ })
+ .splice(2, 2)
+ .seq(function (a, b, e) {
+ assert.eql(arguments.length, 3);
+ assert.eql([a,b,e], ['a','b','e']);
+ assert.eql(this.stack, ['a','b','e']);
+ this.pass(null);
+ })
+ .reverse()
+ .seq(function (a, b, e){
+ assert.eql(arguments.length, 3);
+ assert.eql([a,b,e], ['e','b','a']);
+ assert.eql(this.stack, ['e','b','a']);
+ this.pass(null);
+ })
+ .map(function(ch){ return ch.toUpperCase(); })
+ .seq(function (A, B, E){
+ assert.eql(arguments.length, 3);
+ assert.eql([A,B,E], ['E','B','A']);
+ assert.eql(this.stack, ['E','B','A']);
+ this.pass(null);
+ })
+ .reduce(function(s, ch){ return s + ':' + ch; })
+ .seq(function (acc){
+ assert.eql(arguments.length, 1);
+ assert.eql(acc, 'E:B:A');
+ assert.eql(this.stack, ['E:B:A']);
+ this.pass(null);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ this(null);
+ })
+ ;
+};
+
+exports.ap = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ var cmp = [1,2,3];
+ Seq.ap([1,2,3])
+ .seqEach(function (x) {
+ assert.eql(cmp.shift(), x);
+ this(null);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(cmp, []);
+ })
+ ;
+
+ assert.throws(function () {
+ Seq.ap({ a : 1, b : 2 });
+ });
+};
+
+exports.seqBind = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ Seq([4,5])
+ .seq(function (a, b, c, d) {
+ assert.eql(a, 2);
+ assert.eql(b, 3);
+ assert.eql(c, 4);
+ assert.eql(d, 5);
+ this(null);
+ }, 2, 3)
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.parBind = function () {
+ var t1 = setTimeout(function () {
+ assert.fail('1 never finished');
+ }, 500);
+ var t2 = setTimeout(function () {
+ assert.fail('2 never finished');
+ }, 500);
+ var t3 = setTimeout(function () {
+ assert.fail('3 never finished');
+ }, 500);
+
+ Seq(['c'])
+ .par(function (a, b, c) {
+ clearTimeout(t1);
+ assert.eql(a, 'a');
+ assert.eql(b, 'b');
+ assert.eql(c, 'c');
+ this(null);
+ }, 'a', 'b')
+ .par(function (x, c) {
+ clearTimeout(t2);
+ assert.eql(x, 'x');
+ assert.eql(c, 'c');
+ this(null);
+ }, 'x')
+ .seq(function () {
+ clearTimeout(t3);
+ })
+ ;
+};
+
+exports.emptySeqEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('never finished');
+ }, 100);
+
+ Seq()
+ .seqEach(function (x) {
+ assert.fail('no elements');
+ })
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.emptyForEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ Seq()
+ .forEach(function () {
+ assert.fail('non-empty stack');
+ })
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.emptyParEach = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ Seq()
+ .parEach(function () {
+ assert.fail('non-empty stack');
+ })
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.emptyParMap = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ Seq()
+ .parMap(function () {
+ assert.fail('non-empty stack');
+ })
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.emptySeqMap = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ Seq()
+ .seqMap(function () {
+ assert.fail('non-empty stack');
+ })
+ .seq(function () {
+ clearTimeout(to);
+ })
+ ;
+};
+
+exports.ok = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ function moo1 (cb) { cb(3) }
+ function moo2 (cb) { cb(4) }
+
+ Seq()
+ .par(function () { moo1(this.ok) })
+ .par(function () { moo2(this.ok) })
+ .seq(function (x, y) {
+ clearTimeout(to);
+ assert.eql(x, 3);
+ assert.eql(y, 4);
+ })
+ ;
+};
+
+exports.nextOk = function () {
+ var to = setTimeout(function () {
+ assert.fail('seq never fired');
+ }, 500);
+
+ function moo1 (cb) { cb(3) }
+ function moo2 (cb) { cb(4) }
+
+ Seq()
+ .par_(function (next) { moo1(next.ok) })
+ .par_(function (next) { moo2(next.ok) })
+ .seq_(function (next, x, y) {
+ assert.eql(x, 3);
+ assert.eql(y, 4);
+ next.ok([ 1, 2, 3 ])
+ })
+ .flatten()
+ .parMap_(function (next, x) {
+ next.ok(x * 100)
+ })
+ .seq_(function (next) {
+ clearTimeout(to);
+ assert.deepEqual(next.stack, [ 100, 200, 300 ]);
+ })
+ ;
+};
+
+exports.regressionTestForAccidentalDeepTraversalOfTheContext = function () {
+ // Create a single-item stack with a bunch of references to other objects:
+ var stack = [{}];
+ for (var i = 0 ; i < 10000 ; i += 1) {
+ stack[0][i] = stack[0];
+ }
+
+ var startTime = new Date(),
+ numCalled = 0,
+ to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 1000);
+
+ Seq(stack)
+ .parEach(function (item) {
+ numCalled += 1;
+ this();
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(numCalled, 1);
+ assert.ok((new Date().getTime() - startTime) < 1000, 'if this test takes longer than a second, the bug must have been reintroduced');
+ });
+};
diff --git a/test/seq_.js b/test/seq_.js
new file mode 100644
index 0000000..369cc86
--- /dev/null
+++ b/test/seq_.js
@@ -0,0 +1,149 @@
+var Seq = require('seq');
+var assert = require('assert');
+
+exports.seq_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ Seq(['xxx'])
+ .seq_('pow', function (next, x) {
+ assert.eql(next, this);
+ assert.eql(x, 'xxx');
+ next(null, 'yyy');
+ })
+ .seq(function (y) {
+ clearTimeout(to);
+ assert.eql(y, 'yyy');
+ assert.eql(this.vars.pow, 'yyy');
+ })
+ ;
+};
+
+exports.par_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ Seq()
+ .par_(function (next) {
+ assert.eql(next, this);
+ next(null, 111);
+ })
+ .par_(function (next) {
+ assert.eql(next, this);
+ next(null, 222);
+ })
+ .seq(function (x, y) {
+ clearTimeout(to);
+ assert.eql(x, 111);
+ assert.eql(y, 222);
+ })
+ ;
+};
+
+exports.forEach_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ var acc = [];
+ Seq([7,8,9])
+ .forEach_(function (next, x) {
+ assert.eql(next, this);
+ acc.push(x);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(acc, [ 7, 8, 9 ]);
+ })
+ ;
+};
+
+exports.seqEach_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ var acc = [];
+ Seq([7,8,9])
+ .seqEach_(function (next, x) {
+ assert.eql(next, this);
+ acc.push(x);
+ setTimeout(function () {
+ next(null, x);
+ }, Math.random() * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(acc, [ 7, 8, 9 ]);
+ assert.eql(this.stack, [ 7, 8, 9 ]);
+ })
+ ;
+};
+
+exports.parEach_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ var acc = [];
+ Seq([7,8,9])
+ .parEach_(function (next, x) {
+ assert.eql(next, this);
+ acc.push(x);
+ setTimeout(function () {
+ next(null, x);
+ }, Math.random() * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(acc, [ 7, 8, 9 ]);
+ assert.eql(this.stack, [ 7, 8, 9 ]);
+ })
+ ;
+};
+
+exports.seqMap_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ var acc = [];
+ Seq([7,8,9])
+ .seqMap_(function (next, x) {
+ assert.eql(next, this);
+ acc.push(x);
+ setTimeout(function () {
+ next(null, x * 10);
+ }, Math.random() * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(acc, [ 7, 8, 9 ]);
+ assert.eql(this.stack, [ 70, 80, 90 ]);
+ })
+ ;
+};
+
+exports.parMap_ = function () {
+ var to = setTimeout(function () {
+ assert.fail('never got to the end of the chain');
+ }, 5000);
+
+ var acc = [];
+ Seq([7,8,9])
+ .parMap_(function (next, x) {
+ assert.eql(next, this);
+ acc.push(x);
+ setTimeout(function () {
+ next(null, x * 10);
+ }, Math.random() * 10);
+ })
+ .seq(function () {
+ clearTimeout(to);
+ assert.eql(acc, [ 7, 8, 9 ]);
+ assert.eql(this.stack, [ 70, 80, 90 ]);
+ })
+ ;
+};
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-seq.git
More information about the Pkg-javascript-commits
mailing list