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" ], |
267
README.md
# 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(); | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29328
607
376