src: revert domain using AsyncListeners
[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   this._events = this._events || {};
53   this._maxListeners = this._maxListeners || undefined;
54 };
55
56 // Obviously not all Emitters should be limited to 10. This function allows
57 // that to be increased. Set to zero for unlimited.
58 EventEmitter.prototype.setMaxListeners = function(n) {
59   if (!util.isNumber(n) || n < 0 || isNaN(n))
60     throw TypeError('n must be a positive number');
61   this._maxListeners = n;
62   return this;
63 };
64
65 EventEmitter.prototype.emit = function(type) {
66   var er, handler, len, args, i, listeners;
67
68   if (!this._events)
69     this._events = {};
70
71   // If there is no 'error' event listener then throw.
72   if (type === 'error' && !this._events.error) {
73     er = arguments[1];
74     if (this.domain) {
75       if (!er)
76         er = new Error('Uncaught, unspecified "error" event.');
77       er.domainEmitter = this;
78       er.domain = this.domain;
79       er.domainThrown = false;
80       this.domain.emit('error', er);
81     } else if (er instanceof Error) {
82       throw er; // Unhandled 'error' event
83     } else {
84       throw Error('Uncaught, unspecified "error" event.');
85     }
86     return false;
87   }
88
89   handler = this._events[type];
90
91   if (util.isUndefined(handler))
92     return false;
93
94   if (this.domain && this !== process)
95     this.domain.enter();
96
97   if (util.isFunction(handler)) {
98     switch (arguments.length) {
99       // fast cases
100       case 1:
101         handler.call(this);
102         break;
103       case 2:
104         handler.call(this, arguments[1]);
105         break;
106       case 3:
107         handler.call(this, arguments[1], arguments[2]);
108         break;
109       // slower
110       default:
111         len = arguments.length;
112         args = new Array(len - 1);
113         for (i = 1; i < len; i++)
114           args[i - 1] = arguments[i];
115         handler.apply(this, args);
116     }
117   } else if (util.isObject(handler)) {
118     len = arguments.length;
119     args = new Array(len - 1);
120     for (i = 1; i < len; i++)
121       args[i - 1] = arguments[i];
122
123     listeners = handler.slice();
124     len = listeners.length;
125     for (i = 0; i < len; i++)
126       listeners[i].apply(this, args);
127   }
128
129   if (this.domain && this !== process)
130     this.domain.exit();
131
132   return true;
133 };
134
135 EventEmitter.prototype.addListener = function(type, listener) {
136   var m;
137
138   if (!util.isFunction(listener))
139     throw TypeError('listener must be a function');
140
141   if (!this._events)
142     this._events = {};
143
144   // To avoid recursion in the case that type === "newListener"! Before
145   // adding it to the listeners, first emit "newListener".
146   if (this._events.newListener)
147     this.emit('newListener', type,
148               util.isFunction(listener.listener) ?
149               listener.listener : listener);
150
151   if (!this._events[type])
152     // Optimize the case of one listener. Don't need the extra array object.
153     this._events[type] = listener;
154   else if (util.isObject(this._events[type]))
155     // If we've already got an array, just append.
156     this._events[type].push(listener);
157   else
158     // Adding the second element, need to change to array.
159     this._events[type] = [this._events[type], listener];
160
161   // Check for listener leak
162   if (util.isObject(this._events[type]) && !this._events[type].warned) {
163     var m;
164     if (!util.isUndefined(this._maxListeners)) {
165       m = this._maxListeners;
166     } else {
167       m = EventEmitter.defaultMaxListeners;
168     }
169
170     if (m && m > 0 && this._events[type].length > m) {
171       this._events[type].warned = true;
172       console.error('(node) warning: possible EventEmitter memory ' +
173                     'leak detected. %d listeners added. ' +
174                     'Use emitter.setMaxListeners() to increase limit.',
175                     this._events[type].length);
176       console.trace();
177     }
178   }
179
180   return this;
181 };
182
183 EventEmitter.prototype.on = EventEmitter.prototype.addListener;
184
185 EventEmitter.prototype.once = function(type, listener) {
186   if (!util.isFunction(listener))
187     throw TypeError('listener must be a function');
188
189   var fired = false;
190
191   function g() {
192     this.removeListener(type, g);
193
194     if (!fired) {
195       fired = true;
196       listener.apply(this, arguments);
197     }
198   }
199
200   g.listener = listener;
201   this.on(type, g);
202
203   return this;
204 };
205
206 // emits a 'removeListener' event iff the listener was removed
207 EventEmitter.prototype.removeListener = function(type, listener) {
208   var list, position, length, i;
209
210   if (!util.isFunction(listener))
211     throw TypeError('listener must be a function');
212
213   if (!this._events || !this._events[type])
214     return this;
215
216   list = this._events[type];
217   length = list.length;
218   position = -1;
219
220   if (list === listener ||
221       (util.isFunction(list.listener) && list.listener === listener)) {
222     delete this._events[type];
223     if (this._events.removeListener)
224       this.emit('removeListener', type, listener);
225
226   } else if (util.isObject(list)) {
227     for (i = length; i-- > 0;) {
228       if (list[i] === listener ||
229           (list[i].listener && list[i].listener === listener)) {
230         position = i;
231         break;
232       }
233     }
234
235     if (position < 0)
236       return this;
237
238     if (list.length === 1) {
239       list.length = 0;
240       delete this._events[type];
241     } else {
242       list.splice(position, 1);
243     }
244
245     if (this._events.removeListener)
246       this.emit('removeListener', type, listener);
247   }
248
249   return this;
250 };
251
252 EventEmitter.prototype.removeAllListeners = function(type) {
253   var key, listeners;
254
255   if (!this._events)
256     return this;
257
258   // not listening for removeListener, no need to emit
259   if (!this._events.removeListener) {
260     if (arguments.length === 0)
261       this._events = {};
262     else if (this._events[type])
263       delete this._events[type];
264     return this;
265   }
266
267   // emit removeListener for all listeners on all events
268   if (arguments.length === 0) {
269     for (key in this._events) {
270       if (key === 'removeListener') continue;
271       this.removeAllListeners(key);
272     }
273     this.removeAllListeners('removeListener');
274     this._events = {};
275     return this;
276   }
277
278   listeners = this._events[type];
279
280   if (util.isFunction(listeners)) {
281     this.removeListener(type, listeners);
282   } else if (Array.isArray(listeners)) {
283     // LIFO order
284     while (listeners.length)
285       this.removeListener(type, listeners[listeners.length - 1]);
286   }
287   delete this._events[type];
288
289   return this;
290 };
291
292 EventEmitter.prototype.listeners = function(type) {
293   var ret;
294   if (!this._events || !this._events[type])
295     ret = [];
296   else if (util.isFunction(this._events[type]))
297     ret = [this._events[type]];
298   else
299     ret = this._events[type].slice();
300   return ret;
301 };
302
303 EventEmitter.listenerCount = function(emitter, type) {
304   var ret;
305   if (!emitter._events || !emitter._events[type])
306     ret = 0;
307   else if (util.isFunction(emitter._events[type]))
308     ret = 1;
309   else
310     ret = emitter._events[type].length;
311   return ret;
312 };