pax_global_header00006660000000000000000000000064126712375270014526gustar00rootroot0000000000000052 comment=8178eee3e2b2723526255193fb13b237259a6975 node-seq-0.3.5/000077500000000000000000000000001267123752700132465ustar00rootroot00000000000000node-seq-0.3.5/.npmignore000066400000000000000000000000151267123752700152410ustar00rootroot00000000000000node_modules node-seq-0.3.5/README.markdown000066400000000000000000000273761267123752700157660ustar00rootroot00000000000000Seq === 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. node-seq-0.3.5/examples/000077500000000000000000000000001267123752700150645ustar00rootroot00000000000000node-seq-0.3.5/examples/join.js000066400000000000000000000006731267123752700163670ustar00rootroot00000000000000var 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 ]) }) ; node-seq-0.3.5/examples/parseq.coffee000066400000000000000000000006061267123752700175320ustar00rootroot00000000000000fs = 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') ) node-seq-0.3.5/examples/parseq.js000066400000000000000000000007171267123752700167220ustar00rootroot00000000000000var 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'); }) ; node-seq-0.3.5/examples/stat_all.coffee000066400000000000000000000005271267123752700200440ustar00rootroot00000000000000fs = 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 ) node-seq-0.3.5/examples/stat_all.js000066400000000000000000000006161267123752700172300ustar00rootroot00000000000000var 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); }) ; node-seq-0.3.5/index.js000077500000000000000000000375151267123752700147310ustar00rootroot00000000000000var 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); }; } node-seq-0.3.5/package.json000066400000000000000000000016271267123752700155420ustar00rootroot00000000000000{ "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@substack.net", "url" : "http://substack.net" }, "license" : "MIT/X11", "engine" : { "node" : ">=0.4.0" } } node-seq-0.3.5/test/000077500000000000000000000000001267123752700142255ustar00rootroot00000000000000node-seq-0.3.5/test/readdir.js000066400000000000000000000016021267123752700161740ustar00rootroot00000000000000var 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) ; }; node-seq-0.3.5/test/seq.js000066400000000000000000000600651267123752700153620ustar00rootroot00000000000000var 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'); }); }; node-seq-0.3.5/test/seq_.js000066400000000000000000000072361267123752700155220ustar00rootroot00000000000000var 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 ]); }) ; };