API Docs for: 0.0.2
Show:

File: lib/auto.js

/**
 * auto Module
 *
 * -
 *
 * @module auto
 * @constructor
 * @author potanin@UD
 * @date 8/5/13
 * @type {Object}
 */
function auto( tasks, callback, settings ) {

  // Ensure always using new instance of auto
  if( !( this instanceof auto ) ) {

    if( arguments.length === 0 ) {
      return {};
    }

    if( arguments.length === 1 ) {
      return new auto( tasks );
    }

    if( arguments.length === 2 ) {
      return new auto( tasks, callback );
    }

    if( arguments.length === 3 ) {
      return new auto( tasks, callback, settings );
    }

  }

  // Set private properties
  var self        = this;

  // Set instance properties
  self.id         = Math.random().toString( 36 ).substring( 7 );
  self.tasks      = tasks;
  self.callback   = arguments[1] instanceof Function ? arguments[1] : function defaultCallback() {};
  self.settings   = auto.extend( {}, auto.defaults, arguments.length === 3 ? settings : 'function' !== typeof callback ? callback : {} );
  self.response   = {};
  self.listeners  = [];
  self._meta      = { started: new Date().getTime(), timeout: new Date().getTime() + self.settings.timeout };
  self.error      = null;
  self.keys       = Object.keys( self.tasks );

  // Extend this with Event Emitter
  auto.emitter.mixin( self );

  // Ensure there are tasks
  if( !self.keys.length ) {
    process.nextTick( function() { self.emit( 'complete', null, {} ); });
    return self.callback( null );
  }

  // Add to running queue
  auto.active[ self.id ] = self;

  // Add final listener
  self.addListener( self.onComplete );

  // Iterate through keys
  self.each( self.keys, self.taskIterator );

  return self;

}

/**
 * Instance Properties.
 *
 */
Object.defineProperties( auto.prototype, {
  taskIterator: {
    value: function taskIterator( key ) {

      var self      = this;
      var task      = this.tasks[key] instanceof Function ? [ this.tasks[key] ] : this.tasks[key];
      var requires  = task.slice( 0, Math.abs( task.length - 1 )) || [];

      // Task Step Context
      var context = {
        id: self.id,
        task: key,
        requires: requires,
        response: self.response,
        tasks: self.tasks
      }

      /**
       * Task Callback
       *
       * @todo Migrate into prototype.
       * @param error
       */
      function taskCallback( error ) {

        // Get response arguments
        var args = Array.prototype.slice.call( arguments, 1) ;

        if (args.length <= 1) {
          args = args[0];
        }

        if( error && error instanceof Error ) {
          var safeResults = {};

          auto.each( Object.keys( self.response ), function( rkey ) {
            safeResults[rkey] = self.response[rkey];
          });

          safeResults[key] = args;

          // Emit task evnet and complete event
          self.emit( 'error', error, safeResults );
          self.emit( 'complete', error, safeResults );

          // Remove from active queue
          delete auto.active[ this.id ];

          // Trigger callback
          self.callback( error, safeResults );

          // stop subsequent errors hitting callback multiple times
          self.callback = function __fake_callback__() {};

        } else {

          // Save task response to general response
          self.response[key] = args;

          // process.nextTick( )
          self.setImmediate( self.stepComplete.bind( self ), key, args );

        }

      };

      /**
       * Ready to Process a Step
       *
       * @todo Migrate into prototype.
       * @returns {*|boolean}
       */
      function ready() {

        // Identify Dependacncies with some form of magic
        var magic = self.reduce( requires, function( a, x ) {
          return ( a && self.response.hasOwnProperty( x ));
        }, true ) && !self.response.hasOwnProperty( key );

        // Step Ready
        self.emit( 'ready', key, magic );

        return magic;

      };

      // Trigger Method
      if( ready() ) {

        task[ task.length - 1 ].bind( context )( taskCallback, self.response, self );

      } else {

        // Create a listener to be checked later
        self.addListener( function listener() {

          if( ready() ) {
            self.removeListener( listener, key );
            task[ task.length - 1 ].bind( context )( taskCallback, self.response );
          }

        }, key );

      }

    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  onComplete: {
    value: function onComplete() {

      if( Object.keys( this.response ).length !== this.keys.length ) {
        return;
      }

      // Will fire multiple times if not checked
      if( this.callback.name === 'Placeholder' ) {
        return;
      }

      // All steps in task are complete
      this.emit( 'complete', null, this.response );
      this.emit( 'success', this.response );

      // Call the primary callback
      this.callback( null, this.response );

      // Remove from active queue
      delete auto.active[ this.id ];

      // Unset Callback
      this.callback = function Placeholder() {};

    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  removeListener: {
    /**
     * Remove Listener from Queue
     *
     * @method removeListener
     * @param fn
     * @param k
     */
    value: function removeListener( fn, k ) {
      // self.emit( 'removeListener', k );

      for( var i = 0; i < this.listeners.length; i += 1 ) {
        if( this.listeners[i] === fn ) { this.listeners.splice(i, 1); return; }
      }

    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  setImmediate: {
    /**
     * Run Method on next tick
     *
     * @method setImmediate
     * @param fn
     * @returns {*}
     */
    value: function setImmediate( fn ) {

      if( process && process.nextTick ) {
        return process.nextTick( fn );
      }

      setTimeout( function() { fn() }, 0 )

    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  each: {
    /**
     * Array Iterator
     *
     * @method each
     * @param arr
     * @param iterator
     * @returns {*}
     */
    value: function each( arr, iterator ) {
      if (arr.forEach) { return arr.forEach( iterator.bind( this ) ); }
      for (var i = 0; i < arr.length; i += 1) { iterator.bind( this )(arr[i], i, arr); }
    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  reduce: {
    /**
     * Array Reduce
     *
     * @method reduce
     * @param arr
     * @param iterator
     * @param memo
     * @returns {*}
     */
    value: function reduce( arr, iterator, memo ) {

      if( arr.reduce) {
        return arr.reduce( iterator , memo);
      }

      this.each( arr, function (x, i, a) {
        memo = iterator(memo, x, i, a);
      });

      return memo;
    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  addListener: {
    /**
     * Add Listener to Queue in context
     *
     * @method addListener
     * @param fn
     * @param k
     */
    value: function addListener( fn , k) {
      // self.emit( 'addListener', k );
      this.listeners.unshift( fn.bind( this ) );
    },
    writable: false,
    enumerable: false,
    configurable: true
  },
  stepComplete: {
    /**
     * Single Step Complete
     *
     * @method stepComplete
     * @param k
     * @param args
     */
    value: function stepComplete( k, args ) {
      // self.emit( 'step_complete', k, args );

      // Get just the methods from each step
      this.each( this.listeners.slice(0), function( fn ) {
        fn();
      });

    },
    writable: false,
    enumerable: false,
    configurable: true
  }
});

/**
 * Constructor Properties
 *
 */
Object.defineProperties( module.exports = auto, {
  middleware: {
    /**
     *
     * @param tasks
     * @param callback
     * @param settings
     * @returns {Function}
     */
    value: function middleware( tasks, callback, settings ) {

      return function middleware( req, res, next ) {

        var instance = auto( tasks, callback, settings );

        instance.on( 'success', function complete( report ) {
          res.send( report );
        });

        instance.on( 'error', function error( error, report ) {
          next( error );
        });

      }

    },
    enumerable: true,
    writable: true,
    configurable: false
  },
  defaults: {
    value: {
      timeout: 5000
    },
    enumerable: true,
    writable: true,
    configurable: false
  },
  emitter: {
    value: require( 'object-emitter' ),
    writable: true,
    enumerable: false
  },
  extend: {
    value: require( 'extend' ),
    enumerable: false,
    writable: true
  },
  active: {
    value: {},
    enumerable: true,
    configurable: false,
    writable: true
  },
});