Simplifies asynchronous control flow in javascript making making parallel code, synchronous code, and error handling simple
npm install ctrl
this which does not play well with coffeescript's bound function (()=>) syntax.
ctrl to which you pass an array of functions to call.
step object which allows you to control the flow of the program. The signature for the ctrl function is
javascript
ctrl(arrayOfSteps, optionsObject={}, callback=function(){})
`
Lets look at a simple example
`javascript
var steps = [
function (step) {
console.log('working on the first thing');
setTimeout(step.next, 300);
},
function (step) {
console.log('working on second thing whos callback returns stuff');
setTimeout(step.next, 300, 'someString', 6969);
},
function (step, someString, someNumber) {
console.log('callback argument 1: ' + someString);
console.log('callback argument 2: ' + someNumber);
step.next();
}
];
//ctrl = require('ctrl');
ctrl(steps, {}, function (step) {
console.log('we are done!');
});
//The above code will print the following to console
//"working on the first thing"
//"working on second thing whos callback returns stuff"
//"callback argument 1: someString"
//"callback argument 2: 6969"
//"we are done!"
`
You can play with this example at JSBin.
Error handling
__________________________________
Any errors that are thrown can be handled by an error handler
`javascript
var errorHandler = function(step, error){
console.log(error);
};
steps = [
function (step) {
console.log('working on the first thing');
setTimeout(step.next, 300);
},
function (step) {
console.log('i am going to throw an error');
throw "error!";
},
function (step) {
console.log("This function should never be called because there was an errror!");
}
];
//ctrl = require('ctrl');
ctrl(steps, {errorHandler:errorHandler}, function (step) {
console.log('This should not be called cause there was an error!');
});
//The above code will print the following to console
//"working on the first thing"
//"i am going to throw an error"
//"error!"
`
You can play with this example at JSBin.
Parallel code
_____________________________________
Using the spawn function on the step object you can run tasks in parallel and the next step will only be
run when all of the tasks are complete
`javascript
steps = [
function (step) {
console.log('I am going to spawn 3!');
setTimeout(step.spawn(), 300);
setTimeout(step.spawn(), 500, 'string');
setTimeout(step.spawn(), 100, 1,2);
step.next();//call step.next to signal that we are done spawing
},
//return values are returned in the same order spawn is called in.
//All the return values for a given function are returned as a single parameter
//If no values are returned null is returned if multible values are returned an array is returned
function (step, arg1, arg2, arg3) {
console.log(arg1);//arg1 = null
console.log(arg2);//arg2 = 'string'
console.log(arg3);//arg3 = [1,2]
step.next();
}
];
//ctrl = require('ctrl');
ctrl(steps, {}, function (step) {
console.log('we are done!');
});
//The above code will print the following to console
//"I am going to spawn 3!"
//null
//"string"
//[1, 2]
//"we are done!"
`
You can play with this example at JSBin.
Sharing state
________________________
You can share state by using step.data which is passed to all the functions.
`javascript
steps = [
function (step) {
console.log(step.data.shared);
step.data.shared = "string has been changed once";
step.next();
},
function (step) {
console.log(step.data.shared);
step.data.shared = "string has been changed twice";
step.next();
}
];
//ctrl = require('ctrl');
ctrl(steps, {data:{shared:'inital string'}}, function (step) {
console.log(step.data.shared);
console.log('we are done!');
});
//The above code will print the following to console
//"inital string"
//"string has been changed once"
//"string has been changed twice"
//"we are done!"
`
You can play with this example at JSBin.
Extending
_______________________
If you have ever written custom middleware for connect then you should be familiar with the mechanism by which ctrl can be extended. The step object is built up layer by layer by a series of builder functions. Each builder function extends the step object in some specific way. The signature for a builder function is function(step, next). An example should clarify things.
`javascript
//The logger builder logs all parameters passed to step.next
var loggingBuilder = function(step, next){
var oldNext = step.next;
step.next = function(){
//print the arguments passed to step.next
console.log("Next was called with the parameters:");
console.log(Array.prototype.slice.call(arguments));
oldNext.apply(null, arguments); //call the original step
};
next(); //dont forget to call next to signal that your custom builder is done
};
//create a new CtrlRunner. When you call Ctrl() you are using
//the default runner by createing a new runner we can customize
//what builders are used to construct the step parameter.
var customRunner = new ctrl.CtrlRunner(ctrl.builders.next, ctrl.builders.spawn,
loggingBuilder, ctrl.builders.data, ctrl.builders.errorHandler);
//we are now going to run the new CtrlRunner that has our custom builder
customRunner.run([
function (step) {
step.next("parm1", "parm2");
},
function (step) {
step.next(1, 2);
}
], {}, function(step){console.log('we are done!');});
//The above code will print the following to console:
//"Next was called with the parameters:"
//[]
//"Next was called with the parameters:"
//["parm1", "parm2"]
//"Next was called with the parameters:"
//[1, 2]
//Notice next was called 3 time. Next is called initially to start running the steps
``