ctrl

Simplifies asynchronous control flow in javascript making making parallel code, synchronous code, and error handling simple

npm install ctrl
2 downloads in the last week
7 downloads in the last month

Build Status

ctrl

Simplifies asynchronous control flow in coffeescript making parallel code, synchronous code, and error handling simple

Why make another control flow library?


  • One often desires to pass additional state to all the functions. Most of the control flow libraries I have seen do not allow for this. The issue can be worked around with a closure but closures are exactly what we are trying to avoid!
  • There are tons of control flow libraries out there but none of them seem to play well with coffeescript; they all override this which does not play well with coffeescript's bound function (()=>) syntax.

Features


  • Makes it easy to write synchronous code without nested callbacks
  • Makes it easy to write parallel code without keeping track of all the pesky callbacks
  • Allows all your functions to easily share state without using a closure
  • Designed to be compatible with coffeescript's cool features
  • Works in nodejs and the browser
  • Is very extensible

How to install


$ npm install ctrl

Basic usage


ctrl is really easy to use and consists of a single function, ctrl to which you pass an array of functions to call. The first parameter to each function is a reference to a step object which allows you to control the flow of the program. The signature for the ctrl function is

ctrl(arrayOfSteps, optionsObject={}, callback=function(){})

Lets look at a simple example

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

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

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.

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.

//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

You can play with this example at JSBin.

The api documentation for the library can be found here.

npm loves you