UNPKG

6.17 kBJavaScriptView Raw
1// Licensed to the Software Freedom Conservancy (SFC) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The SFC licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18'use strict';
19
20/**
21 * Describes an event listener registered on an {@linkplain EventEmitter}.
22 */
23class Listener {
24 /**
25 * @param {!Function} fn The acutal listener function.
26 * @param {(Object|undefined)} scope The object in whose scope to invoke the
27 * listener.
28 * @param {boolean} oneshot Whether this listener should only be used once.
29 */
30 constructor(fn, scope, oneshot) {
31 this.fn = fn;
32 this.scope = scope;
33 this.oneshot = oneshot;
34 }
35}
36
37
38/** @type {!WeakMap<!EventEmitter, !Map<string, !Set<!Listener>>>} */
39const EVENTS = new WeakMap;
40
41
42/**
43 * Object that can emit events for others to listen for.
44 */
45class EventEmitter {
46 /**
47 * Fires an event and calls all listeners.
48 * @param {string} type The type of event to emit.
49 * @param {...*} var_args Any arguments to pass to each listener.
50 */
51 emit(type, var_args) {
52 let events = EVENTS.get(this);
53 if (!events) {
54 return;
55 }
56
57 let args = Array.prototype.slice.call(arguments, 1);
58
59 let listeners = events.get(type);
60 if (listeners) {
61 for (let listener of listeners) {
62 listener.fn.apply(listener.scope, args);
63 if (listener.oneshot) {
64 listeners.delete(listener);
65 }
66 }
67 }
68 }
69
70 /**
71 * Returns a mutable list of listeners for a specific type of event.
72 * @param {string} type The type of event to retrieve the listeners for.
73 * @return {!Set<!Listener>} The registered listeners for the given event
74 * type.
75 */
76 listeners(type) {
77 let events = EVENTS.get(this);
78 if (!events) {
79 events = new Map;
80 EVENTS.set(this, events);
81 }
82
83 let listeners = events.get(type);
84 if (!listeners) {
85 listeners = new Set;
86 events.set(type, listeners);
87 }
88 return listeners;
89 }
90
91 /**
92 * Registers a listener.
93 * @param {string} type The type of event to listen for.
94 * @param {!Function} fn The function to invoke when the event is fired.
95 * @param {Object=} opt_self The object in whose scope to invoke the listener.
96 * @param {boolean=} opt_oneshot Whether the listener should b (e removed after
97 * the first event is fired.
98 * @return {!EventEmitter} A self reference.
99 * @private
100 */
101 addListener_(type, fn, opt_self, opt_oneshot) {
102 let listeners = this.listeners(type);
103 for (let listener of listeners) {
104 if (listener.fn === fn) {
105 return this;
106 }
107 }
108 listeners.add(new Listener(fn, opt_self || undefined, !!opt_oneshot));
109 return this;
110 }
111
112 /**
113 * Registers a listener.
114 * @param {string} type The type of event to listen for.
115 * @param {!Function} fn The function to invoke when the event is fired.
116 * @param {Object=} opt_self The object in whose scope to invoke the listener.
117 * @return {!EventEmitter} A self reference.
118 */
119 addListener(type, fn, opt_self) {
120 return this.addListener_(type, fn, opt_self, false);
121 }
122
123 /**
124 * Registers a one-time listener which will be called only the first time an
125 * event is emitted, after which it will be removed.
126 * @param {string} type The type of event to listen for.
127 * @param {!Function} fn The function to invoke when the event is fired.
128 * @param {Object=} opt_self The object in whose scope to invoke the listener.
129 * @return {!EventEmitter} A self reference.
130 */
131 once(type, fn, opt_self) {
132 return this.addListener_(type, fn, opt_self, true);
133 }
134
135 /**
136 * An alias for {@link #addListener() addListener()}.
137 * @param {string} type The type of event to listen for.
138 * @param {!Function} fn The function to invoke when the event is fired.
139 * @param {Object=} opt_self The object in whose scope to invoke the listener.
140 * @return {!EventEmitter} A self reference.
141 */
142 on(type, fn, opt_self) {
143 return this.addListener(type, fn, opt_self);
144 }
145
146 /**
147 * Removes a previously registered event listener.
148 * @param {string} type The type of event to unregister.
149 * @param {!Function} listenerFn The handler function to remove.
150 * @return {!EventEmitter} A self reference.
151 */
152 removeListener(type, listenerFn) {
153 if (typeof type !== 'string' || typeof listenerFn !== 'function') {
154 throw TypeError('invalid args: expected (string, function), got ('
155 + (typeof type) + ', ' + (typeof listenerFn) + ')');
156 }
157
158 let events = EVENTS.get(this);
159 if (!events) {
160 return this;
161 }
162
163 let listeners = events.get(type);
164 if (!listeners) {
165 return this;
166 }
167
168 let match;
169 for (let listener of listeners) {
170 if (listener.fn === listenerFn) {
171 match = listener;
172 break;
173 }
174 }
175 if (match) {
176 listeners.delete(match);
177 if (!listeners.size) {
178 events.delete(type);
179 }
180 }
181 return this;
182 }
183
184 /**
185 * Removes all listeners for a specific type of event. If no event is
186 * specified, all listeners across all types will be removed.
187 * @param {string=} opt_type The type of event to remove listeners from.
188 * @return {!EventEmitter} A self reference.
189 */
190 removeAllListeners(opt_type) {
191 let events = EVENTS.get(this);
192 if (events) {
193 if (typeof opt_type === 'string') {
194 events.delete(opt_type);
195 } else {
196 EVENTS.delete(this);
197 }
198 }
199 return this;
200 }
201}
202
203
204// PUBLIC API
205
206
207module.exports = {
208 EventEmitter: EventEmitter,
209 Listener: Listener
210};