0cd841b8445a91da19a372a62163cc034f2bec2d
[platform/upstream/nodejs.git] / lib / events.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var domain;
23 var util = require('util');
24
25 function EventEmitter() {
26   EventEmitter.init.call(this);
27 }
28 module.exports = EventEmitter;
29
30 // Backwards-compat with node 0.10.x
31 EventEmitter.EventEmitter = EventEmitter;
32
33 EventEmitter.usingDomains = false;
34
35 EventEmitter.prototype.domain = undefined;
36 EventEmitter.prototype._events = undefined;
37 EventEmitter.prototype._maxListeners = undefined;
38
39 // By default EventEmitters will print a warning if more than 10 listeners are
40 // added to it. This is a useful default which helps finding memory leaks.
41 EventEmitter.defaultMaxListeners = 10;
42
43 EventEmitter.init = function() {
44   this.domain = null;
45   if (EventEmitter.usingDomains) {
46     // if there is an active domain, then attach to it.
47     domain = domain || require('domain');
48     if (domain.active && !(this instanceof domain.Domain)) {
49       this.domain = domain.active;
50     }
51   }
52
53   if (!this._events || this._events === Object.getPrototypeOf(this)._events)
54     this._events = {};
55
56   this._maxListeners = this._maxListeners || undefined;
57 };
58
59 // Obviously not all Emitters should be limited to 10. This function allows
60 // that to be increased. Set to zero for unlimited.
61 EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
62   if (!util.isNumber(n) || n < 0 || isNaN(n))
63     throw TypeError('n must be a positive number');
64   this._maxListeners = n;
65   return this;
66 };
67
68 EventEmitter.prototype.emit = function emit(type) {
69   var er, handler, len, args, i, listeners;
70
71   if (!this._events)
72     this._events = {};
73
74   // If there is no 'error' event listener then throw.
75   if (type === 'error' && !this._events.error) {
76     er = arguments[1];
77     if (this.domain) {
78       if (!er)
79         er = new Error('Uncaught, unspecified "error" event.');
80       er.domainEmitter = this;
81       er.domain = this.domain;
82       er.domainThrown = false;
83       this.domain.emit('error', er);
84     } else if (er instanceof Error) {
85       throw er; // Unhandled 'error' event
86     } else {
87       throw Error('Uncaught, unspecified "error" event.');
88     }
89     return false;
90   }
91
92   handler = this._events[type];
93
94   if (util.isUndefined(handler))
95     return false;
96
97   if (this.domain && this !== process)
98     this.domain.enter();
99
100   if (util.isFunction(handler)) {
101     switch (arguments.length) {
102       // fast cases
103       case 1:
104         handler.call(this);
105         break;
106       case 2:
107         handler.call(this, arguments[1]);
108         break;
109       case 3:
110         handler.call(this, arguments[1], arguments[2]);
111         break;
112       // slower
113       default:
114         len = arguments.length;
115         args = new Array(len - 1);
116         for (i = 1; i < len; i++)
117           args[i - 1] = arguments[i];
118         handler.apply(this, args);
119     }
120   } else if (util.isObject(handler)) {
121     len = arguments.length;
122     args = new Array(len - 1);
123     for (i = 1; i < len; i++)
124       args[i - 1] = arguments[i];
125
126     listeners = handler.slice();
127     len = listeners.length;
128     for (i = 0; i < len; i++)
129       listeners[i].apply(this, args);
130   }
131
132   if (this.domain && this !== process)
133     this.domain.exit();
134
135   return true;
136 };
137
138 EventEmitter.prototype.addListener = function addListener(type, listener) {
139   var m;
140
141   if (!util.isFunction(listener))
142     throw TypeError('listener must be a function');
143
144   if (!this._events)
145     this._events = {};
146
147   // To avoid recursion in the case that type === "newListener"! Before
148   // adding it to the listeners, first emit "newListener".
149   if (this._events.newListener)
150     this.emit('newListener', type,
151               util.isFunction(listener.listener) ?
152               listener.listener : listener);
153
154   if (!this._events[type])
155     // Optimize the case of one listener. Don't need the extra array object.
156     this._events[type] = listener;
157   else if (util.isObject(this._events[type]))
158     // If we've already got an array, just append.
159     this._events[type].push(listener);
160   else
161     // Adding the second element, need to change to array.
162     this._events[type] = [this._events[type], listener];
163
164   // Check for listener leak
165   if (util.isObject(this._events[type]) && !this._events[type].warned) {
166     var m;
167     if (!util.isUndefined(this._maxListeners)) {
168       m = this._maxListeners;
169     } else {
170       m = EventEmitter.defaultMaxListeners;
171     }
172
173     if (m && m > 0 && this._events[type].length > m) {
174       this._events[type].warned = true;
175       console.error('(node) warning: possible EventEmitter memory ' +
176                     'leak detected. %d %s listeners added. ' +
177                     'Use emitter.setMaxListeners() to increase limit.',
178                     this._events[type].length, type);
179       console.trace();
180     }
181   }
182
183   return this;
184 };
185
186 EventEmitter.prototype.on = EventEmitter.prototype.addListener;
187
188 EventEmitter.prototype.once = function once(type, listener) {
189   if (!util.isFunction(listener))
190     throw TypeError('listener must be a function');
191
192   var fired = false;
193
194   function g() {
195     this.removeListener(type, g);
196
197     if (!fired) {
198       fired = true;
199       listener.apply(this, arguments);
200     }
201   }
202
203   g.listener = listener;
204   this.on(type, g);
205
206   return this;
207 };
208
209 // emits a 'removeListener' event iff the listener was removed
210 EventEmitter.prototype.removeListener =
211     function removeListener(type, listener) {
212   var list, position, length, i;
213
214   if (!util.isFunction(listener))
215     throw TypeError('listener must be a function');
216
217   if (!this._events || !this._events[type])
218     return this;
219
220   list = this._events[type];
221   length = list.length;
222   position = -1;
223
224   if (list === listener ||
225       (util.isFunction(list.listener) && list.listener === listener)) {
226     delete this._events[type];
227     if (this._events.removeListener)
228       this.emit('removeListener', type, listener);
229
230   } else if (util.isObject(list)) {
231     for (i = length; i-- > 0;) {
232       if (list[i] === listener ||
233           (list[i].listener && list[i].listener === listener)) {
234         position = i;
235         break;
236       }
237     }
238
239     if (position < 0)
240       return this;
241
242     if (list.length === 1) {
243       list.length = 0;
244       delete this._events[type];
245     } else {
246       list.splice(position, 1);
247     }
248
249     if (this._events.removeListener)
250       this.emit('removeListener', type, listener);
251   }
252
253   return this;
254 };
255
256 EventEmitter.prototype.removeAllListeners =
257     function removeAllListeners(type) {
258   var key, listeners;
259
260   if (!this._events)
261     return this;
262
263   // not listening for removeListener, no need to emit
264   if (!this._events.removeListener) {
265     if (arguments.length === 0)
266       this._events = {};
267     else if (this._events[type])
268       delete this._events[type];
269     return this;
270   }
271
272   // emit removeListener for all listeners on all events
273   if (arguments.length === 0) {
274     for (key in this._events) {
275       if (key === 'removeListener') continue;
276       this.removeAllListeners(key);
277     }
278     this.removeAllListeners('removeListener');
279     this._events = {};
280     return this;
281   }
282
283   listeners = this._events[type];
284
285   if (util.isFunction(listeners)) {
286     this.removeListener(type, listeners);
287   } else if (Array.isArray(listeners)) {
288     // LIFO order
289     while (listeners.length)
290       this.removeListener(type, listeners[listeners.length - 1]);
291   }
292   delete this._events[type];
293
294   return this;
295 };
296
297 EventEmitter.prototype.listeners = function listeners(type) {
298   var ret;
299   if (!this._events || !this._events[type])
300     ret = [];
301   else if (util.isFunction(this._events[type]))
302     ret = [this._events[type]];
303   else
304     ret = this._events[type].slice();
305   return ret;
306 };
307
308 EventEmitter.listenerCount = function(emitter, type) {
309   var ret;
310   if (!emitter._events || !emitter._events[type])
311     ret = 0;
312   else if (util.isFunction(emitter._events[type]))
313     ret = 1;
314   else
315     ret = emitter._events[type].length;
316   return ret;
317 };