sequential_executor.js
  1  var AWS = require('./core');
  2  
  3  /**
  4   * @api private
  5   * @!method on(eventName, callback)
  6   *   Registers an event listener callback for the event given by `eventName`.
  7   *   Parameters passed to the callback function depend on the individual event
  8   *   being triggered. See the event documentation for those parameters.
  9   *
 10   *   @param eventName [String] the event name to register the listener for
 11   *   @param callback [Function] the listener callback function
 12   *   @param toHead [Boolean] attach the listener callback to the head of callback array if set to true.
 13   *     Default to be false.
 14   *   @return [AWS.SequentialExecutor] the same object for chaining
 15   */
 16  AWS.SequentialExecutor = AWS.util.inherit({
 17  
 18    constructor: function SequentialExecutor() {
 19      this._events = {};
 20    },
 21  
 22    /**
 23     * @api private
 24     */
 25    listeners: function listeners(eventName) {
 26      return this._events[eventName] ? this._events[eventName].slice(0) : [];
 27    },
 28  
 29    on: function on(eventName, listener, toHead) {
 30      if (this._events[eventName]) {
 31        toHead ?
 32          this._events[eventName].unshift(listener) :
 33          this._events[eventName].push(listener);
 34      } else {
 35        this._events[eventName] = [listener];
 36      }
 37      return this;
 38    },
 39  
 40    onAsync: function onAsync(eventName, listener, toHead) {
 41      listener._isAsync = true;
 42      return this.on(eventName, listener, toHead);
 43    },
 44  
 45    removeListener: function removeListener(eventName, listener) {
 46      var listeners = this._events[eventName];
 47      if (listeners) {
 48        var length = listeners.length;
 49        var position = -1;
 50        for (var i = 0; i < length; ++i) {
 51          if (listeners[i] === listener) {
 52            position = i;
 53          }
 54        }
 55        if (position > -1) {
 56          listeners.splice(position, 1);
 57        }
 58      }
 59      return this;
 60    },
 61  
 62    removeAllListeners: function removeAllListeners(eventName) {
 63      if (eventName) {
 64        delete this._events[eventName];
 65      } else {
 66        this._events = {};
 67      }
 68      return this;
 69    },
 70  
 71    /**
 72     * @api private
 73     */
 74    emit: function emit(eventName, eventArgs, doneCallback) {
 75      if (!doneCallback) doneCallback = function() { };
 76      var listeners = this.listeners(eventName);
 77      var count = listeners.length;
 78      this.callListeners(listeners, eventArgs, doneCallback);
 79      return count > 0;
 80    },
 81  
 82    /**
 83     * @api private
 84     */
 85    callListeners: function callListeners(listeners, args, doneCallback, prevError) {
 86      var self = this;
 87      var error = prevError || null;
 88  
 89      function callNextListener(err) {
 90        if (err) {
 91          error = AWS.util.error(error || new Error(), err);
 92          if (self._haltHandlersOnError) {
 93            return doneCallback.call(self, error);
 94          }
 95        }
 96        self.callListeners(listeners, args, doneCallback, error);
 97      }
 98  
 99      while (listeners.length > 0) {
100        var listener = listeners.shift();
101        if (listener._isAsync) { // asynchronous listener
102          listener.apply(self, args.concat([callNextListener]));
103          return; // stop here, callNextListener will continue
104        } else { // synchronous listener
105          try {
106            listener.apply(self, args);
107          } catch (err) {
108            error = AWS.util.error(error || new Error(), err);
109          }
110          if (error && self._haltHandlersOnError) {
111            doneCallback.call(self, error);
112            return;
113          }
114        }
115      }
116      doneCallback.call(self, error);
117    },
118  
119    /**
120     * Adds or copies a set of listeners from another list of
121     * listeners or SequentialExecutor object.
122     *
123     * @param listeners [map<String,Array<Function>>, AWS.SequentialExecutor]
124     *   a list of events and callbacks, or an event emitter object
125     *   containing listeners to add to this emitter object.
126     * @return [AWS.SequentialExecutor] the emitter object, for chaining.
127     * @example Adding listeners from a map of listeners
128     *   emitter.addListeners({
129     *     event1: [function() { ... }, function() { ... }],
130     *     event2: [function() { ... }]
131     *   });
132     *   emitter.emit('event1'); // emitter has event1
133     *   emitter.emit('event2'); // emitter has event2
134     * @example Adding listeners from another emitter object
135     *   var emitter1 = new AWS.SequentialExecutor();
136     *   emitter1.on('event1', function() { ... });
137     *   emitter1.on('event2', function() { ... });
138     *   var emitter2 = new AWS.SequentialExecutor();
139     *   emitter2.addListeners(emitter1);
140     *   emitter2.emit('event1'); // emitter2 has event1
141     *   emitter2.emit('event2'); // emitter2 has event2
142     */
143    addListeners: function addListeners(listeners) {
144      var self = this;
145  
146      // extract listeners if parameter is an SequentialExecutor object
147      if (listeners._events) listeners = listeners._events;
148  
149      AWS.util.each(listeners, function(event, callbacks) {
150        if (typeof callbacks === 'function') callbacks = [callbacks];
151        AWS.util.arrayEach(callbacks, function(callback) {
152          self.on(event, callback);
153        });
154      });
155  
156      return self;
157    },
158  
159    /**
160     * Registers an event with {on} and saves the callback handle function
161     * as a property on the emitter object using a given `name`.
162     *
163     * @param name [String] the property name to set on this object containing
164     *   the callback function handle so that the listener can be removed in
165     *   the future.
166     * @param (see on)
167     * @return (see on)
168     * @example Adding a named listener DATA_CALLBACK
169     *   var listener = function() { doSomething(); };
170     *   emitter.addNamedListener('DATA_CALLBACK', 'data', listener);
171     *
172     *   // the following prints: true
173     *   console.log(emitter.DATA_CALLBACK == listener);
174     */
175    addNamedListener: function addNamedListener(name, eventName, callback, toHead) {
176      this[name] = callback;
177      this.addListener(eventName, callback, toHead);
178      return this;
179    },
180  
181    /**
182     * @api private
183     */
184    addNamedAsyncListener: function addNamedAsyncListener(name, eventName, callback, toHead) {
185      callback._isAsync = true;
186      return this.addNamedListener(name, eventName, callback, toHead);
187    },
188  
189    /**
190     * Helper method to add a set of named listeners using
191     * {addNamedListener}. The callback contains a parameter
192     * with a handle to the `addNamedListener` method.
193     *
194     * @callback callback function(add)
195     *   The callback function is called immediately in order to provide
196     *   the `add` function to the block. This simplifies the addition of
197     *   a large group of named listeners.
198     *   @param add [Function] the {addNamedListener} function to call
199     *     when registering listeners.
200     * @example Adding a set of named listeners
201     *   emitter.addNamedListeners(function(add) {
202     *     add('DATA_CALLBACK', 'data', function() { ... });
203     *     add('OTHER', 'otherEvent', function() { ... });
204     *     add('LAST', 'lastEvent', function() { ... });
205     *   });
206     *
207     *   // these properties are now set:
208     *   emitter.DATA_CALLBACK;
209     *   emitter.OTHER;
210     *   emitter.LAST;
211     */
212    addNamedListeners: function addNamedListeners(callback) {
213      var self = this;
214      callback(
215        function() {
216          self.addNamedListener.apply(self, arguments);
217        },
218        function() {
219          self.addNamedAsyncListener.apply(self, arguments);
220        }
221      );
222      return this;
223    }
224  });
225  
226  /**
227   * {on} is the prefered method.
228   * @api private
229   */
230  AWS.SequentialExecutor.prototype.addListener = AWS.SequentialExecutor.prototype.on;
231  
232  /**
233   * @api private
234   */
235  module.exports = AWS.SequentialExecutor;