VulpesOS/gaia/apps/email/js/evt.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

167 lines
4.7 KiB
JavaScript
Executable file

/*
* Custom events lib. Notable features:
*
* - the module itself is an event emitter. Useful for "global" pub/sub.
* - evt.mix can be used to mix in an event emitter into existing object.
* - notification of listeners is done in a try/catch, so all listeners
* are notified even if one fails. Errors are thrown async via setTimeout
* so that all the listeners can be notified without escaping from the
* code via a throw within the listener group notification.
* - new evt.Emitter() can be used to create a new instance of an
* event emitter.
* - Uses "this" internally, so always call object with the emitter args.
*/
'use strict';
define(function() {
var evt,
slice = Array.prototype.slice,
props = ['_events', '_pendingEvents', 'on', 'once', 'latest',
'latestOnce', 'removeListener', 'emitWhenListener', 'emit'];
function Emitter() {
this._events = {};
this._pendingEvents = {};
}
Emitter.prototype = {
on: function(id, fn) {
var listeners = this._events[id],
pending = this._pendingEvents[id];
if (!listeners) {
listeners = this._events[id] = [];
}
listeners.push(fn);
if (pending) {
pending.forEach(function(args) {
fn.apply(null, args);
});
delete this._pendingEvents[id];
}
return this;
},
once: function(id, fn) {
var self = this,
fired = false;
function one() {
if (fired) {
return;
}
fired = true;
fn.apply(null, arguments);
// Remove at a further turn so that the event
// forEach in emit does not get modified during
// this turn.
setTimeout(function() {
self.removeListener(id, one);
});
}
return this.on(id, one);
},
/**
* Waits for a property on the object that has the event interface
* to be available. That property MUST EVALUATE TO A TRUTHY VALUE.
* hasOwnProperty is not used because many objects are created with
* null placeholders to give a proper JS engine shape to them, and
* this method should not trigger the listener for those cases.
* If the property is already available, call the listener right
* away. If not available right away, listens for an event name that
* matches the property name.
* @param {String} id property name.
* @param {Function} fn listener.
*/
latest: function(id, fn) {
if (this[id] && !this._pendingEvents[id]) {
fn(this[id]);
}
this.on(id, fn);
},
/**
* Same as latest, but only calls the listener once.
* @param {String} id property name.
* @param {Function} fn listener.
*/
latestOnce: function(id, fn) {
if (this[id] && !this._pendingEvents[id]) {
fn(this[id]);
} else {
this.once(id, fn);
}
},
removeListener: function(id, fn) {
var i,
listeners = this._events[id];
if (listeners) {
i = listeners.indexOf(fn);
if (i !== -1) {
listeners.splice(i, 1);
}
if (listeners.length === 0) {
delete this._events[id];
}
}
},
/**
* Like emit, but if no listeners yet, holds on
* to the value until there is one. Any other
* args after first one are passed to listeners.
* @param {String} id event ID.
*/
emitWhenListener: function(id) {
var listeners = this._events[id];
if (listeners) {
this.emit.apply(this, arguments);
} else {
if (!this._pendingEvents[id]) {
this._pendingEvents[id] = [];
}
this._pendingEvents[id].push(slice.call(arguments, 1));
}
},
emit: function(id) {
var args = slice.call(arguments, 1),
listeners = this._events[id];
if (listeners) {
listeners.forEach(function(fn) {
try {
fn.apply(null, args);
} catch (e) {
// Throw at later turn so that other listeners
// can complete. While this messes with the
// stack for the error, continued operation is
// valued more in this tradeoff.
// This also means we do not need to .catch()
// for the wrapping promise.
setTimeout(function() {
throw e;
});
}
});
}
}
};
evt = new Emitter();
evt.Emitter = Emitter;
evt.mix = function(obj) {
var e = new Emitter();
props.forEach(function(prop) {
if (obj.hasOwnProperty(prop)) {
throw new Error('Object already has a property "' + prop + '"');
}
obj[prop] = e[prop];
});
return obj;
};
return evt;
});