Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Documentation updates; seqFilter, parFilter

  • Loading branch information...
commit 45765b9213a1da7107dbcd107f7a4fc268f4fe2c 1 parent aae0227
@dsc dsc authored
Showing with 288 additions and 8 deletions.
  1. +79 −7 README.markdown
  2. +106 −1 index.js
  3. +103 −0 test/seq.js
View
86 README.markdown
@@ -7,6 +7,10 @@ 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
========
@@ -67,8 +71,11 @@ Output:
Groups: substack : substack dialout cdrom floppy audio src video plugdev games netdev fuse www
This file has 464 bytes
-Methods
-=======
+
+
+
+API
+===
Each method executes callbacks with a context (its `this`) described in the next
section. Every method returns `this`.
@@ -78,6 +85,8 @@ 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=[])
----------
@@ -86,6 +95,7 @@ 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)
@@ -116,6 +126,7 @@ Seq()
which prints an array of files in `__dirname`.
+
.par(cb)
--------
.par(key, cb, *args)
@@ -140,6 +151,7 @@ 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)
----------
@@ -161,6 +173,7 @@ This default error handler looks like this:
})
````
+
.forEach(cb)
------------
@@ -175,6 +188,7 @@ index.
`forEach` is a sequential operation like `seq` and won't run until all pending
parallel requests yield results.
+
.seqEach(cb)
------------
@@ -188,6 +202,7 @@ 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)
@@ -207,12 +222,14 @@ 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)
@@ -221,9 +238,50 @@ these values.
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.
+Create a new nested context. `cb`'s first argument is the previous context, and `this`
+is the nested `Seq` object.
+
.flatten(fully=true)
--------------------
@@ -231,27 +289,32 @@ Create a new nested context. `cb`'s first argument is the previous context.
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.
+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(...)
@@ -282,8 +345,9 @@ Seq([1, 2, 3])
+
Explicit Parameters
-===================
+-------------------
For environments like coffee-script or nested logic where threading `this` is
bothersome, you can use:
@@ -299,8 +363,10 @@ bothersome, you can use:
which work exactly like their un-underscored counterparts except for the first
parameter to the supplied callback is set to the context, `this`.
-Context
-=======
+
+
+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:
@@ -347,6 +413,8 @@ this.error
This is used for error propagation. You probably shouldn't mess with it.
+
+
Installation
============
@@ -363,8 +431,12 @@ 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.
+
+
View
107 index.js
@@ -333,7 +333,112 @@ function builder (saw, xs) {
});
};
- [ 'forEach', 'seqEach', 'parEach', 'seqMap', 'parMap' ]
+ /**
+ * 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 () {
View
103 test/seq.js
@@ -500,6 +500,7 @@ exports.seqMap = function () {
;
};
+
exports.seqMapInto = function () {
var to = setTimeout(function () {
assert.fail('never finished');
@@ -525,6 +526,108 @@ exports.seqMapInto = function () {
;
};
+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');
Please sign in to comment.
Something went wrong with that request. Please try again.