API Docs for: 0.0.1
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;
  var keys      = Object.keys( tasks );

  // Set instance properties
  this.id          = Math.random().toString( 36 ).substring( 7 );
  this.error       = null;
  this.tasks       = tasks;
  this.callback    = arguments[1] instanceof Function ? arguments[1] : function defaultCallback() {};
  this.settings    = Auto.extend( {}, Auto.defaults, arguments.length === 3 ? settings : 'function' !== typeof callback ? callback : {} );
  this.response    = {};
  this.listeners   = [];

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

  // Ensure there are tasks
  if( !keys.length ) {
    return callback( null );
  }

  self.addListener( function() { if( Object.keys( self.response ).length === keys.length ) {

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

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

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

    // Clear out objects
    // self._events = null;

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

  }});

  Auto.each( keys, function ( key ) {

    // Get Task Object
    var task = ( tasks[key] instanceof Function ) ? [tasks[key]]: tasks[key];

    // Step is Complete
    var taskCallback = 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 );

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

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

      } else {

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

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

      }

    };

    var requires = task.slice( 0, Math.abs( task.length - 1 )) || [];

    // Ready to Process a Step
    var ready = function ready() {

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

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

      return magic;

    };

    if (ready()) {
      // Trigger Method

      task[ task.length - 1 ].bind({
        todo: true
      })( taskCallback, self.response, self );

    } else {

      // Create a listener to be checked later
      var listener = function listener() {

        if (ready()) {
          self.removeListener( listener, key );

          task[task.length - 1].bind({
            todo: true
          })( taskCallback, self.response );

        }
      }

      self.addListener( listener, key );

    }

  });

}

Object.defineProperties( Auto.prototype, {
  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; }
      }

    },
    enumerable: false
  },
  addListener: {
    /**
     * Add Listener to Queue
     *
     * @method addListener
     * @param fn
     * @param k
     */
    value: function addListener(fn, k) {
      // self.emit( 'addListener', k );
      this.listeners.unshift( fn );
    },
    enumerable: false
  },
  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
      Auto.each( this.listeners.slice(0), function( fn ) {
        fn();
      });

    }
  }
});

Object.defineProperties( module.exports = Auto, {
  defaults: {
    value: {
      timeout: 5000
    },
    enumerable: true,
    writable: true,
    configurable: false
  },
  emitter: {
    value: require( 'object-emitter' ),
    enumerable: false
  },
  setImmediate: {
    value: function setImmediate( fn ) {

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

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

    },
    enumerable: false
  },
  each: {
    value: function each(arr, iterator) {
      if (arr.forEach) { return arr.forEach(iterator); }
      for (var i = 0; i < arr.length; i += 1) { iterator(arr[i], i, arr); }
    },
    writable: true
  },
  reduce: {
    value: function reduce(arr, iterator, memo) {
      if (arr.reduce) { return arr.reduce(iterator, memo); }
      Auto.each(arr, function (x, i, a) { memo = iterator(memo, x, i, a); });
      return memo;
    },
    writable: true
  },
  extend: {
    value: require( 'extend' ),
    enumerable: false
  },
  active: {
    value: {},
    enumerable: true,
    writable: true,
    configurable: false
  },
});