events: optimize adding and removing of listeners
[platform/upstream/nodejs.git] / lib / events.js
1 'use strict';
2
3 var domain;
4
5 function EventEmitter() {
6   EventEmitter.init.call(this);
7 }
8 module.exports = EventEmitter;
9
10 // Backwards-compat with node 0.10.x
11 EventEmitter.EventEmitter = EventEmitter;
12
13 EventEmitter.usingDomains = false;
14
15 EventEmitter.prototype.domain = undefined;
16 EventEmitter.prototype._events = undefined;
17 EventEmitter.prototype._maxListeners = undefined;
18
19 // By default EventEmitters will print a warning if more than 10 listeners are
20 // added to it. This is a useful default which helps finding memory leaks.
21 EventEmitter.defaultMaxListeners = 10;
22
23 EventEmitter.init = function() {
24   this.domain = null;
25   if (EventEmitter.usingDomains) {
26     // if there is an active domain, then attach to it.
27     domain = domain || require('domain');
28     if (domain.active && !(this instanceof domain.Domain)) {
29       this.domain = domain.active;
30     }
31   }
32
33   if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
34     this._events = {};
35     this._eventsCount = 0;
36   }
37
38   this._maxListeners = this._maxListeners || undefined;
39 };
40
41 // Obviously not all Emitters should be limited to 10. This function allows
42 // that to be increased. Set to zero for unlimited.
43 EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
44   if (typeof n !== 'number' || n < 0 || isNaN(n))
45     throw new TypeError('n must be a positive number');
46   this._maxListeners = n;
47   return this;
48 };
49
50 function $getMaxListeners(that) {
51   if (that._maxListeners === undefined)
52     return EventEmitter.defaultMaxListeners;
53   return that._maxListeners;
54 }
55
56 EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
57   return $getMaxListeners(this);
58 };
59
60 // These standalone emit* functions are used to optimize calling of event
61 // handlers for fast cases because emit() itself often has a variable number of
62 // arguments and can be deoptimized because of that. These functions always have
63 // the same number of arguments and thus do not get deoptimized, so the code
64 // inside them can execute faster.
65 function emitNone(handler, isFn, self) {
66   if (isFn)
67     handler.call(self);
68   else {
69     var len = handler.length;
70     var listeners = arrayClone(handler, len);
71     for (var i = 0; i < len; ++i)
72       listeners[i].call(self);
73   }
74 }
75 function emitOne(handler, isFn, self, arg1) {
76   if (isFn)
77     handler.call(self, arg1);
78   else {
79     var len = handler.length;
80     var listeners = arrayClone(handler, len);
81     for (var i = 0; i < len; ++i)
82       listeners[i].call(self, arg1);
83   }
84 }
85 function emitTwo(handler, isFn, self, arg1, arg2) {
86   if (isFn)
87     handler.call(self, arg1, arg2);
88   else {
89     var len = handler.length;
90     var listeners = arrayClone(handler, len);
91     for (var i = 0; i < len; ++i)
92       listeners[i].call(self, arg1, arg2);
93   }
94 }
95 function emitThree(handler, isFn, self, arg1, arg2, arg3) {
96   if (isFn)
97     handler.call(self, arg1, arg2, arg3);
98   else {
99     var len = handler.length;
100     var listeners = arrayClone(handler, len);
101     for (var i = 0; i < len; ++i)
102       listeners[i].call(self, arg1, arg2, arg3);
103   }
104 }
105
106 function emitMany(handler, isFn, self, args) {
107   if (isFn)
108     handler.apply(self, args);
109   else {
110     var len = handler.length;
111     var listeners = arrayClone(handler, len);
112     for (var i = 0; i < len; ++i)
113       listeners[i].apply(self, args);
114   }
115 }
116
117 EventEmitter.prototype.emit = function emit(type) {
118   var er, handler, len, args, i, events, domain;
119   var needDomainExit = false;
120   var doError = (type === 'error');
121
122   events = this._events;
123   if (events)
124     doError = (doError && events.error == null);
125   else if (!doError)
126     return false;
127
128   domain = this.domain;
129
130   // If there is no 'error' event listener then throw.
131   if (doError) {
132     er = arguments[1];
133     if (domain) {
134       if (!er)
135         er = new Error('Uncaught, unspecified "error" event.');
136       er.domainEmitter = this;
137       er.domain = domain;
138       er.domainThrown = false;
139       domain.emit('error', er);
140     } else if (er instanceof Error) {
141       throw er; // Unhandled 'error' event
142     } else {
143       throw Error('Uncaught, unspecified "error" event.');
144     }
145     return false;
146   }
147
148   handler = events[type];
149
150   if (!handler)
151     return false;
152
153   if (domain && this !== process) {
154     domain.enter();
155     needDomainExit = true;
156   }
157
158   var isFn = typeof handler === 'function';
159   len = arguments.length;
160   switch (len) {
161     // fast cases
162     case 1:
163       emitNone(handler, isFn, this);
164       break;
165     case 2:
166       emitOne(handler, isFn, this, arguments[1]);
167       break;
168     case 3:
169       emitTwo(handler, isFn, this, arguments[1], arguments[2]);
170       break;
171     case 4:
172       emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
173       break;
174     // slower
175     default:
176       args = new Array(len - 1);
177       for (i = 1; i < len; i++)
178         args[i - 1] = arguments[i];
179       emitMany(handler, isFn, this, args);
180   }
181
182   if (needDomainExit)
183     domain.exit();
184
185   return true;
186 };
187
188 EventEmitter.prototype.addListener = function addListener(type, listener) {
189   var m;
190   var events;
191   var existing;
192
193   if (typeof listener !== 'function')
194     throw new TypeError('listener must be a function');
195
196   events = this._events;
197   if (!events) {
198     events = this._events = {};
199     this._eventsCount = 0;
200   } else {
201     // To avoid recursion in the case that type === "newListener"! Before
202     // adding it to the listeners, first emit "newListener".
203     if (events.newListener) {
204       this.emit('newListener', type,
205                 listener.listener ? listener.listener : listener);
206
207       // Re-assign `events` because a newListener handler could have caused the
208       // this._events to be assigned to a new object
209       events = this._events;
210     }
211     existing = events[type];
212   }
213
214   if (!existing) {
215     // Optimize the case of one listener. Don't need the extra array object.
216     existing = events[type] = listener;
217     ++this._eventsCount;
218   } else {
219     if (typeof existing === 'function') {
220       // Adding the second element, need to change to array.
221       existing = events[type] = [existing, listener];
222     } else {
223       // If we've already got an array, just append.
224       existing.push(listener);
225     }
226
227     // Check for listener leak
228     if (!existing.warned) {
229       m = $getMaxListeners(this);
230       if (m && m > 0 && existing.length > m) {
231         existing.warned = true;
232         console.error('(node) warning: possible EventEmitter memory ' +
233                       'leak detected. %d %s listeners added. ' +
234                       'Use emitter.setMaxListeners() to increase limit.',
235                       existing.length, type);
236         console.trace();
237       }
238     }
239   }
240
241   return this;
242 };
243
244 EventEmitter.prototype.on = EventEmitter.prototype.addListener;
245
246 EventEmitter.prototype.once = function once(type, listener) {
247   if (typeof listener !== 'function')
248     throw new TypeError('listener must be a function');
249
250   var fired = false;
251
252   function g() {
253     this.removeListener(type, g);
254
255     if (!fired) {
256       fired = true;
257       listener.apply(this, arguments);
258     }
259   }
260
261   g.listener = listener;
262   this.on(type, g);
263
264   return this;
265 };
266
267 // emits a 'removeListener' event iff the listener was removed
268 EventEmitter.prototype.removeListener =
269     function removeListener(type, listener) {
270       var list, events, position, i;
271
272       if (typeof listener !== 'function')
273         throw new TypeError('listener must be a function');
274
275       events = this._events;
276       if (!events)
277         return this;
278
279       list = events[type];
280       if (!list)
281         return this;
282
283       if (list === listener || (list.listener && list.listener === listener)) {
284         if (--this._eventsCount === 0)
285           this._events = {};
286         else {
287           delete events[type];
288           if (events.removeListener)
289             this.emit('removeListener', type, listener);
290         }
291       } else if (typeof list !== 'function') {
292         position = -1;
293
294         for (i = list.length; i-- > 0;) {
295           if (list[i] === listener ||
296               (list[i].listener && list[i].listener === listener)) {
297             position = i;
298             break;
299           }
300         }
301
302         if (position < 0)
303           return this;
304
305         if (list.length === 1) {
306           list[0] = undefined;
307           if (--this._eventsCount === 0) {
308             this._events = {};
309             return this;
310           } else
311             delete events[type];
312         } else {
313           spliceOne(list, position);
314         }
315
316         if (events.removeListener)
317           this.emit('removeListener', type, listener);
318       }
319
320       return this;
321     };
322
323 EventEmitter.prototype.removeAllListeners =
324     function removeAllListeners(type) {
325       var listeners, events;
326
327       events = this._events;
328       if (!events)
329         return this;
330
331       // not listening for removeListener, no need to emit
332       if (!events.removeListener) {
333         if (arguments.length === 0) {
334           this._events = {};
335           this._eventsCount = 0;
336         } else if (events[type]) {
337           if (--this._eventsCount === 0)
338             this._events = {};
339           else
340             delete events[type];
341         }
342         return this;
343       }
344
345       // emit removeListener for all listeners on all events
346       if (arguments.length === 0) {
347         var keys = Object.keys(events);
348         for (var i = 0, key; i < keys.length; ++i) {
349           key = keys[i];
350           if (key === 'removeListener') continue;
351           this.removeAllListeners(key);
352         }
353         this.removeAllListeners('removeListener');
354         this._events = {};
355         this._eventsCount = 0;
356         return this;
357       }
358
359       listeners = events[type];
360
361       if (typeof listeners === 'function') {
362         this.removeListener(type, listeners);
363       } else if (listeners) {
364         // LIFO order
365         do {
366           this.removeListener(type, listeners[listeners.length - 1]);
367         } while (listeners[0]);
368       }
369
370       return this;
371     };
372
373 EventEmitter.prototype.listeners = function listeners(type) {
374   var evlistener;
375   var ret;
376   var events = this._events;
377
378   if (!events)
379     ret = [];
380   else {
381     evlistener = events[type];
382     if (!evlistener)
383       ret = [];
384     else if (typeof evlistener === 'function')
385       ret = [evlistener];
386     else
387       ret = arrayClone(evlistener);
388   }
389
390   return ret;
391 };
392
393 EventEmitter.listenerCount = function(emitter, type) {
394   var evlistener;
395   var ret = 0;
396   var events = emitter._events;
397
398   if (events) {
399     evlistener = events[type];
400     if (typeof evlistener === 'function')
401       ret = 1;
402     else if (evlistener)
403       ret = evlistener.length;
404   }
405
406   return ret;
407 };
408
409 // About 1.5x faster than the two-arg version of Array#splice().
410 function spliceOne(list, index) {
411   for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
412     list[i] = list[k];
413   list.pop();
414 }
415
416 function arrayClone(arr, len) {
417   var ret;
418   if (len === undefined)
419     len = arr.length;
420   if (len >= 50)
421     ret = arr.slice();
422   else {
423     ret = new Array(len);
424     for (var i = 0; i < len; i += 1)
425       ret[i] = arr[i];
426   }
427   return ret;
428 }