TwoStep
TwoStep is the spiritual successor of Step with better error handling and finer flow control. Much of the inspiration and direction for this project came from this gist: https://gist.github.com/1524578
The API and Examples
The TwoStep
function takes any number of callbacks as parameters. By default, the callbacks are executed one right after another in the sequence they were passed in. The goal is to reduce the nesting that commonly occurs when chaining several asynchronous functions. The this
pointer in each callback is a object with the following api:
-
this.val([param-name]) -- returns a callback that expects an error object (or null if no error occurred) as the first parameter and a results as the second parameter. Note, this function (as well most other functions in this API) can be called multiple time in a single callback. Each this.val
call corresponds to an addition parameter passed to the next callback. For example, here's how this.val
can be used to asynchronously read in two files:
TwoStep(
function readSelf() {
fs.readFile("./my-file1.txt", this.val("file1"));
fs.readFile("./my-file2.txt", this.val("file2"));
},
function(err, text1, text2) {
}
);
An optional string parameter can passed in to this.val
as well. This string can aid with error handling and debugging. (See Error Handleing section) Note nearly every function in TwoStep
takes the same optional string as the final parameter.
-
this.syncVal(value, [param-name]) -- used to pass a synchronously calulated result to the next callback. Here's an example of how it could be used:
var userId = ;
TwoStep(
function getProfile() {
if(userId in profileCache) {
this.syncVal(profileCache[userId]);
} else {
profileDB.get(userId, this.val());
}
},
function renderProfile(err, profile) { }
);
-
this.valArray([param-name]) -- used when there are a variable number of callbacks. Returns an object with two functions: obj.val
and obj.syncVal
. These two function work very similar to the top level this.val
and this.syncVal
in that calling either results in another item being placed in a list. This is useful, for example, when you need to retrieve multiple user profiles from a database:
var userIds = [ ];
TwoStep(
function getProfiles() {
var profiles = this.valArray();
for(var i = 0; i < userIds.length; i++) {
var userId = userIds[i];
if(userId in profileCache) {
profiles.syncVal(profileCache[userId]);
} else {
profileDB.get(userId, profiles.val());
}
}
},
function renderProfiles(err, profiles) { }
);
-
this.data -- an persistent object that is available to each TwoStep callback.
TwoStep(
function parseUser() { this.data.userId = "123"; },
function getProfile(err, user) { },
function renderProfile(err, profile) {
}
);
-
this.listen(EventEmitter, [param-name]) -- a convenience function that takes an event emitter and listens for data
, error
and end
events on it, then passes the results to the next callback.
TwoStep(
function() {
this.listen(process.stdin);
},
function(err, chunks) {
}
);
-
this.jumpTo(string, [argsArray]) -- allows you to jump to any named function in the current step chain
-
this.jumpTo(function, [argsArray]) -- exits the current step chain and asynchronously call an outside function
function badNews(err) {
}
TwoStep(
function parseData() { },
function modifyUserData(err, val) {
if(err.message === "expired key") { return this.jumpTo("cleanup", [ err ]); }
},
function saveData(err, val) {
if(err) { return this.jumpTo(badNews, [ err ]); }
},
function cleanUp(err, val) {
if(err.message === "expired key") { }
}
);
See test/*.js
for more working examples.
Error Handling
In the hopes to improve debuggability, TwoStep allows users to give each callback a name. When and if an error occurs this name (along with other information) is used to create a "step info object" which is then attatched to the original error object. The "step info object" contains the name of the callback, which step the exception occurred as well the index of the parameter that was being calculated. Here's an example:
TwoStep(
function callDB() {
userdb.get(userId, this.val("user-data"));
productdb.get(productId, this.val("product-data"));
},
function processPage(err, user, product) {
if(err) {
}
},
);
Every TwoStep function (with the exception of jumpTo
) takes an optional string as the final parameter. This string is used to set the paramName
property on the "step info object".
Getting TwoStep
The easiest way to get TwoStep is with npm:
npm install two-step
Alternatively you can clone this git repository:
git clone git://github.com/xavi-/two-step.git
Developed by
License
This project is released under The MIT License.