Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ff

Package Overview
Dependencies
Maintainers
2
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ff - npm Package Compare versions

Comparing version 0.0.8 to 0.0.9

254

lib/ff.js

@@ -28,3 +28,3 @@ /*

// This is ff, by Marcus Cavanaugh and Michael Henretty.
// It was inspired by TwoStep, by Tim Caswell,
// It was inspired by TwoStep by Tim Caswell (https://gist.github.com/1524578),
// which was in turn inspired by Will Conant's flow-js.

@@ -38,4 +38,15 @@

function copyToFunction (group, f) {
for (var method in group) {
f[method] = (function(method) {
return function() {
return group[method].apply(group, arguments);
};
})(method);
}
}
// custom error used to break out of step
function DoneError() {
function DoneError(args) {
this.args = args; // should be passed to result, if exists
this.name = "DoneError";

@@ -53,3 +64,3 @@ this.message = "Group done";

function Group(callback, firstSlotCallback) {
function Group(superGroup, callback, firstSlotCallback) {
this.args = [null];

@@ -60,2 +71,3 @@ this.left = 0;

this.firstSlotCallback = firstSlotCallback; // mcav
this.superGroup = superGroup;
}

@@ -69,3 +81,29 @@

// added by mcav -- break out of the ff
Group.prototype.succeed = function () {
if (this.isDone) return;
this.isDone = true;
this.superGroup.result = [null].concat(slice.call(arguments));
if (!this.superGroup._started) {
// if we didn't start the chain of .next() steps,
// just call the final results immediately.
this.superGroup._runResultHandlers.apply(this.superGroup, this.superGroup.result);
}
};
Group.prototype.fail = function (err) {
if (this.isDone) return;
this.isDone = true;
if (err == null) {
err = new Error("f.fail()");
}
this.superGroup.result = [err];
if (!this.superGroup._started) {
// if we didn't start the chain of .next() steps,
// just call the final results immediately.
this.superGroup._runResultHandlers.apply(this.superGroup, this.superGroup.result);
}
};
// DEPRECATED:
Group.prototype.exit = function exit() {

@@ -85,6 +123,9 @@ if (this.isDone) return;

// Simple utility for passing a sync value to the next step.
Group.prototype.pass = function pass() {
var values = slice.call(arguments);
for (var i = 0, l = values.length; i < l; i++) {
this.args.push(values[i]);
Group.prototype.slot = function () {
if (arguments.length > 0) {
for (var i = 0, l = arguments.length; i < l; i++) {
this.args.push(arguments[i]);
}
} else {
return this.slotMulti(1);
}

@@ -94,3 +135,3 @@ };

// Register a slot in the next step and return a callback
Group.prototype.slot = function slot(argLength) {
Group.prototype.slotMulti = function (argLength) {
this.debug && console.log("slotting", this.left);

@@ -159,3 +200,3 @@ if (!argLength) argLength = 1;

var index = this.args.length++;
var subgroup = new Group(function (err) {
var subgroup = new Group(this.superGroup, function (err) {
if (err) return group.error(err);

@@ -169,3 +210,9 @@ var data = slice.call(arguments, 1);

return constructFFReturnObject(subgroup);
var f = function () {
return subgroup.slot.apply(subgroup, arguments);
};
copyToFunction(subgroup, f);
return f;
};

@@ -177,8 +224,28 @@

Group.prototype.waitNoError = Group.prototype.waitPlain;
Group.prototype.pass = Group.prototype.slot;
// global group
function SuperGroup(context) {
this.currentGroup = null;
function SuperGroup(args) {
var context;
if (typeof args[0] === "function") {
context = null;
} else {
context = args.shift();
}
this.f = null; // the chaining f function
this.currentGroup = new Group(this, function () {});
this.context = context;
this.steps = [];
this.completionHandlers = [];
this._started = false;
this.result = null;
args.forEach(function (fn) {
if (fn._ffMethod) { // backwards-compat
this[fn._ffMethod](fn);
} else {
this.next(fn);
}
}, this);
}

@@ -196,2 +263,6 @@

}
//****************************************************************
// Completion Handlers
/**

@@ -203,4 +274,8 @@ * Call this function regardless of whether or not an error has

SuperGroup.prototype.cb = function (cb) {
this.steps.push(cb);
return this;
if (this.result) {
cb.apply(this, this.result);
} else {
this.completionHandlers.push(cb);
}
return this.f;
}

@@ -211,14 +286,9 @@

* WITHOUT passing any error at all. Again, error won't be null, it'll
* not be passed at all. Your function should only accept the success
* not be passed at all. Your function should only accept the next
* arguments.
*/
SuperGroup.prototype.success = function (cb) {
this.steps.push(function(err) {
if (err) {
throw err;
} else {
cb.apply(this, slice.call(arguments, 1));
}
return this.cb(function(err) {
!err && cb.apply(this, slice.call(arguments, 1));
});
return this;
}

@@ -230,79 +300,113 @@

*/
SuperGroup.prototype.error = function (cb) {
this.steps.push(function(err) {
SuperGroup.prototype.error = SuperGroup.prototype.failure = function (cb) {
return this.cb(function(err) {
err && cb.apply(this, arguments);
});
return this;
});
}
SuperGroup.prototype.next = function (cb) {
this.steps.push(cb);
return this.f;
}
//****************************************************************
// Stepper function
SuperGroup.prototype._next = function() {
var step = this.steps.shift();
if (!step) {
arguments[0] && ff.onerror.apply(null, arguments);
SuperGroup.prototype._execNextStep = function(err) {
if (this.result) {
return;
}
var group = new Group(this._next.bind(this));
try {
// make sure next function isnt applied
// until current function has completed
group.left++;
this.currentGroup = group;
step.apply(this.context || group, arguments);
group.left--;
} catch (e) {
group.left--;
if (e instanceof DoneError) {
return; // don't call anything else.
} else if (e instanceof UncaughtError) {
throw e.exception;
this._started = true;
var step = this.steps.shift();
if (err || !step) {
this._runResultHandlers.apply(this, arguments);
} else {
var group = new Group(this, this._execNextStep.bind(this));
try {
// make sure next function isnt applied
// until current function has completed
group.left++;
this.currentGroup = group;
step.apply(this.context || this, slice.call(arguments, 1));
group.left--;
if (this.result) {
this._runResultHandlers.apply(this, this.result);
return;
}
} catch (e) {
group.left--;
if (e instanceof DoneError) {
return; // don't call anything else (exit()); deprecated.
} else if (e instanceof UncaughtError) {
throw e.exception;
}
group.error(e);
}
group.error(e);
if (group.left === 0) group.done();
}
if (group.left === 0) group.done();
}
function ff() {
var args = slice.call(arguments);
// context is the optional first argument
var context = null;
if (typeof arguments[0] !== "function") {
context = args.shift();
SuperGroup.prototype._runResultHandlers = function (err) {
// if we're running the callback chain, an error occured, and no one
// attached an error handler, log it out with ff.onerror.
if (!this.completionHandlers.length && err && this._started) {
this.completionHandlers.push(ff.onerror);
}
var superGroup = new SuperGroup(context);
this.result = slice.call(arguments);
args.forEach(function (fn) {
if (fn._ffMethod) { // backwards-compat
superGroup[fn._ffMethod](fn);
} else {
superGroup.success(fn);
}
});
this.currentGroup = null;
// fire these in a timeout so that if any handler throws an
// exception, the rest of the handlers will get called, and the
// exception will be reported as unhandled (in node) or in the web
// console (in the browser)
var args = arguments;
this.completionHandlers.forEach(function (handler) {
setTimeout(function () {
handler.apply(this.context || this, args);
}.bind(this), 0);
}, this);
}
//****************************************************************
function ff(context) {
var superGroup = new SuperGroup(slice.call(arguments));
// execute steps in next tick
setTimeout(function(){ superGroup._next(); }, 0);
return constructFFReturnObject(superGroup);
var f = function () {
return superGroup.slot.apply(superGroup, arguments);
};
setTimeout(function(){ superGroup._execNextStep(); }, 0);
superGroup.f = f;
copyToFunction(superGroup, f);
return f;
}
// make the actual object to be returned (the "f" object)
function constructFFReturnObject(group) {
ff.defer = function (context) {
var superGroup = new SuperGroup(slice.call(arguments));
var f = function () {
if (arguments.length == 0) {
return group.slot.apply(group, arguments);
if (!superGroup._started) {
superGroup._execNextStep.apply(superGroup, [null].concat(slice.call(arguments)));
} else {
return group.pass.apply(group, arguments);
return superGroup.slot.apply(superGroup, arguments);
}
};
for (var method in group) {
f[method] = (function(method) {
return function() {
return group[method].apply(group, arguments);
};
})(method);
}
superGroup.f = f;
copyToFunction(superGroup, f);
return f;
}
//****************************************************************
ff.onerror = function(err) {

@@ -309,0 +413,0 @@ throw new UncaughtError(err);

{
"name": "ff",
"version": "0.0.8",
"version": "0.0.9",
"description": "Concise, Powerful Asynchronous Flow Control in JavaScript",

@@ -5,0 +5,0 @@ "engine": [ "node >=0.2.0" ],

# ff: Concise, Powerful Asynchronous Flow Control in JavaScript
***ff* simplifies the most common use cases for series, parallel, and
promise utilities.** It was built because existing async libraries are
too verbose and don't handle errors properly. Don't let your errors go
unhandled. :-)
promise utilities.**
#### Installation
- **Node.JS: `npm install ff`**
- Node.JS: `npm install ff`
- Browsers: Add `lib/ff.js` to your HTML page.

@@ -15,9 +13,10 @@

- [Quick Examples](#quick-examples)
- [API Documentation](#api-documentation)
- [Intro](#intro)
- **[API Documentation](#api-documentation)**
- [Advanced Usage](#advanced-usage)
- [Compared to Other Async Libraries](#compared-to-other-async-libraries)
- [Promise API](#promise-api-deferreds)
- **[Quick Reference & Cheat Sheet](#quick-reference--cheat-sheet)**
- [More Examples](#more-examples)
## Quick Examples
## Intro

@@ -37,5 +36,16 @@ Here's a brief example that shows both serial and parallel steps:

It also supports promises, using the `ff.defer` function [[docs]](#promise-api-deferreds):
```javascript
var f = ff.defer(this);
f.success(function(result, result2) { });
f.error(function (err) { });
f(result, result2); // or f.fail(err);
```
A typical Express web handler looks like this. (Note that even if an
exception gets thrown during one of these handlers, it gets passed
down the chain as an error.)
exception gets thrown during one of these handlers, the .error()
handler will be called.

@@ -94,3 +104,3 @@ ```javascript

The async function should be called with an error as in `callback(err,
result)`. This is an alias for `f.slot()`.
result)`.

@@ -101,4 +111,3 @@ #### `f(arg1, arg2...)`

the next step. This can be useful when you need to pass along a value
directly to the next function synchronously. This is an alias for
`f.pass()`.
directly to the next function synchronously.

@@ -109,13 +118,12 @@ #### `f.wait()`

but you just want to wait until an async call completes successfully.
This behaves exactly like `f()` and `f.slot()`, handling errors, but
no arguments are passed to the next step.
This behaves exactly like `f()`, handling errors, but no arguments are
passed to the next step.
#### `f.slotPlain()`
This is like `f()` and `f.slot()`, except that the resulting callback
must *not* accept an error, as in `callback(result)`. Node's
`fs.exists` doesn't return an error, for instance, and so you must use
`f.slotPlain()` for its callback instead. (If you had used
`f.slot()`, it would have thought `fs.exists` had passed an *error* as
the first argument.
This is like `f()`, except that the resulting callback must *not*
accept an error, as in `callback(result)`. Node's `fs.exists` doesn't
return an error, for instance, and so you must use `f.slotPlain()` for
its callback instead. (If you had used `f.slot()`, it would have
thought `fs.exists` had passed an *error* as the first argument.

@@ -127,2 +135,9 @@ #### `f.waitPlain()`

#### `f.slotMulti(n)`
Like `f()`, except that the resulting callback will pass `n` arguments
to the next step instead of just one. For instance, calling `var cb =
f.slotMulti(2)` followed by `cb(err, rsp, body)` would pass both `rsp`
and `body` as two arguments to the next step.
#### `f.group()`

@@ -135,2 +150,21 @@

#### `f.succeed(successArgs...)`
This causes the chain of steps to end successfully (after you return
from the current function). The result handlers (`.success()` and
`.cb()`) will be called as soon as the current step returns. No other
steps will be executed afterward.
#### `f.fail(err)`
This causes the chain of steps to end as though the given error had
occurred (after you return from the current function). The result
handlers (`.error()` and `.cb()`) will be called as soon as the
current step returns. No other steps will be executed afterward.
#### `f.next(fn)`
You can add additional steps after calling `ff()` using `f.next(fn)`.
Internally, we pass the arguments through this function initially.
## Finally, remember to handle the result! (`.cb`, `.error`, `.success`)

@@ -165,3 +199,3 @@

A `.success()` handler will *only* be called if no error occur ed.
A `.success()` handler will *only* be called if no error occured.
Additionally, an error object will *not* be passed. Only results.

@@ -176,4 +210,5 @@

**Always remember to add either a `.cb()` or `.success()` handler
after your `ff()` call, so that errors propagate!**
**Always remember to add one of these result handlers after your
`ff()` call, so that errors propagate!** You can add multiple result
handlers and they will all be called simultaneously.

@@ -183,8 +218,7 @@ ### Error Handling

If any function throws an exception, or an error gets passed to one of
the callbacks (as in `callback(err, result)`), the error will get
passed down to the next function that can handle the error. In most
cases, this is the `.cb(cb)` function you added at the end. This is an
important feature that a lot of async libraries don't handle properly,
and it ensures that if you specify a `.cb()` or `.error()`, you'll
always pass back a final callback with an error or a result.
the callbacks (as in `callback(err, result)`), the error will be
propagated immediately to your result handlers (`.cb()` and
`.error()`). If a result handler throws an exception, that exception
will bubble up into Node's `unhandledException` handler or the
browser's developer console.

@@ -211,3 +245,3 @@ ---

}, function (allFiles) {
// allFiles now consists of 3 items (the contents of each file).
// allFiles is an array of 3 items (the contents of each file).

@@ -232,4 +266,4 @@ // If any call had returned an err, this function would not be

var f = ff(this);
f.success(one);
f.success(two);
f.next(one);
f.next(two);
f.cb(three);

@@ -239,109 +273,116 @@ ```

Error handling is actually quite simple: If an error occurs in any
step, it gets passed down, skipping over any `.success` handlers.
step, it gets passed down, skipping over any `.next` handlers.
---
# Compared to Other Async Libraries
# Promise API (Deferreds)
Let's say you want to do something simple: Read two files, and
callback whether or not the two files are equal. And we want any
errors to be propagated up to the caller.
Because of the implementation details we just described, `ff` doubles
as a simple promise library using a very similar API. All you need to
remember is to call `ff.defer()` instead of `ff()`.
### Using ff
```javascript
var f = ff.defer(this);
```javascript
function compareFiles(pathA, pathB, cb) {
var f = ff(function () {
fs.readFile(pathA, f());
fs.readFile(pathB, f());
}, function (fileA, fileB) {
f(fileA == fileB); // pass the result to cb
}).cb(cb);
}
// set callbacks:
f.success(function(result, result2) { });
f.error(function (err) { });
// now trigger the result:
f(result, result2); // or f.fail(err);
```
### Using js.io's lib.Callback (promises)
To trigger success or failure:
```javascript
function compareFiles(pathA, pathB, cb) {
var callback = new lib.Callback();
fs.readFile(pathA, callback.chain());
fs.readFile(pathB, callback.chain());
callback.run(function (chains) {
var err = chains[0][0] || chains[1][0];
if (err) {
cb(err);
} else {
cb(null, chains[0][1] == chains[1][1]);
}
});
}
f(arg1, arg2...) // success
f.fail(err) // failure
```
### Using async
```javascript
function compareFiles(pathA, pathB, cb) {
async.parallel({
fileA: function (callback) {
fs.readFile(pathA, callback);
},
fileB: function (callback) {
fs.readFile(pathB, callback);
}
}, function (err, results) {
if (err) {
cb(err);
} else {
cb(null, results.fileA == results.fileB);
}
});
}
```
Just like with a regular `ff` call, you can attach `.success()`,
`.error()`, and `.cb()` handlers.
### Using Basil's common.parallel
You can also pass functions into the `ff.defer(...)` call, just like
regular `ff`:
```javascript
function compareFiles(pathA, pathB, cb) {
var wait = common.parallel(function(results) {
var err = results.err1 || results.err2;
if (err) {
cb(err);
} else {
cb(null, results.fileA == results.fileB);
}
});
fs.readFile(pathA, wait('err1', 'fileA'));
fs.readFile(pathB, wait('err2', 'fileB'));
}
var f = ff.defer(function(result, text) {
// do something with result
}, function () {
// ...etc...
}).cb(cb);
// now fire the result into the first step!
f(result, "something else");
```
Once your chain has succeeded or failed, future `.success()` and
`.error()` handlers will remember the result and fire immediately. The
result is stored on `f.result` once available.
---
# Quick Reference / Cheat Sheet
# More Examples
The [API Documentation](#api-documentation) provides a much more thorough tutorial.
#### Control Flow API Summary
### Serial Execution
```javascript
// Create a chain of steps with the `ff` function:
var f = ff(context, function () {
// Within each method, use the `f` object.
// Most common uses:
f(arg1, arg2); // pass multiple arguments synchronously
fs.readFile("file1.txt", f()); // use f() for async callbacks
fs.readFile("file2.txt", f.wait()); // just wait for the result
// without putting it in args
// To process arrays, use groups:
var group = f.group();
allFiles.forEach(function (item) { // use any `f` function on arrays
fs.readFile(item, group.slot()); // and the result gets stored as
}); // an array in the next step
// Less common uses for atypical functions
fs.exists("file3.txt", f.slotPlain()); // fs.exists doesn't pass an error
fs.exists("file4.txt", f.waitPlain()); // ditto, and I don't care if it fails
var cb = f.slotMulti(2); // slot and pass two arguments to the next function
// for example, cb(null, 1, 2);
// Aborting the chain of steps early:
f.succeed(result1, ...); // after this function, skip the other steps
f.fail(err); // after this function, fail with this error
}, function (arg1, arg2, file1, allFiles, file3Exists, multi1, multi2) {
// Do something amazing here!
}).cb(cb); // <-- usually you'll have someone else handle a (err, result...) callback
var f = ff(function () {
request("POST /auth/", f());
}, function (userID) {
request("GET /users/" + userID, f());
}, function (userInfo) {
request("POST /users/" + userInfo.id, {name: "Spike"}, f.wait());
}, function () {
// all done!
}).cb(cb);
// Don't forget result handlers (often chained to `ff` for conciseness)
f.cb(function (err, args...) { }); // triggered on both success and error
f.success(function (args...) { }); // only on success
f.error(function (err) { }); // only on error
```
#### Promise API Summary
```javascript
// Create a deferred
var f = ff.defer(context);
// Add result handlers:
f.success(function (args...) { });
f.error(function (err) { });
f.cb(function (err, args...) { }); // triggered on both success and error
// Trigger results:
f(arg1, ...); // success
f.fail(err); // failure
// Get the result synchronously, if available (the error argument is on f.result[0])
var resultArray = f.result
```
If you have better examples, please submit them!
## Acknowledgements
---
# Acknowledgements
Made by [Marcus Cavanaugh](http://mcav.com/) and [Michael Henretty](http://twitter.com/mikehenrty).
This code was originally based on
[Tim Caswell](mailto:tim@creationix.com)'s sketch of a
[reimagined Step](https://gist.github.com/1524578#comments) library.
[reimagined](https://gist.github.com/1524578) [Step](https://github.com/creationix/step) library.

@@ -73,2 +73,31 @@ var assert = require("assert");

describe("#succeed()", function () {
it("should work", function (done) {
ff(function () {
this.succeed(2, 3);
}, function () {
assert.fail();
}).cb(function(err, two, three) {
assert(two == 2 && three == 3);
done();
});
});
});
describe("#fail()", function () {
it("should work", function (done) {
ff(function () {
try {
this.fail(4);
} catch(e) {
}
}, function () {
assert.fail();
}).cb(function(err, two) {
assert(err == 4 && !two);
done();
});
});
});
describe("#exceptions()", function () {

@@ -186,2 +215,70 @@ it("should be propagated", function (done) {

});
describe("#defer()", function () {
it("should work", function (done) {
var f = ff.defer(this);
var completed = false;
setTimeout(function() { assert(!completed); }, 20);
f.success(function() { completed = true; });
setTimeout(function() {
f("OK");
}, 30);
setTimeout(function() {
assert(completed);
done();
}, 50);
});
it("should retroactively succeed", function (done) {
var f = ff.defer(this);
f("ok");
f.success(function() {
done();
});
});
it("should retroactively fail", function (done) {
var f = ff.defer(this);
f.fail(4);
f.failure(function(n) {
done();
});
});
it("should call fns", function (done) {
var n = 0;
var f = ff.defer(this, function(x){
assert(x == 2);
f(x);
n++;
}, function (x) {
assert(x == 2);
n++;
}).cb(function(e) {
if (e) throw e;
assert(n == 2);
done();
});
f(2);
});
it("should call multiple handlers", function (done) {
var f = ff.defer(this);
f.fail(4);
f.success(function(n) {
assert.fail();
});
var n = 0;
f.failure(function() {
n++;
});
f.failure(function() {
assert(n == 1);
done();
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc