EventProxy. An implementation of task/event based asynchronous pattern.
A module that can be mixed in to any object in order to provide it with custom events.
You may bind
or unbind
a callback function to an event;trigger
-ing an event fires all callbacks in succession.
Examples:
var render = function (template, resources) {};
var proxy = new EventProxy();
proxy.assign("template", "l10n", render);
proxy.trigger("template", template);
proxy.trigger("l10n", resources);
var EventProxy = function () {
if (!(this instanceof EventProxy)) {
return new EventProxy();
}
this._callbacks = {};
this._fired = {};
};
Bind an event, specified by a string name, ev
, to a callback
function.
Passing ALL_EVENT will bind the callback to all events fired.
Examples:
var proxy = new EventProxy();
proxy.addListener("template", function (event) {
// TODO
});
EventProxy.prototype.addListener = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].push(callback);
return this;
};
addListener
alias, bind
EventProxy.prototype.bind = EventProxy.prototype.addListener;
addListener
alias, on
EventProxy.prototype.on = EventProxy.prototype.addListener;
addListener
alias, subscribe
EventProxy.prototype.subscribe = EventProxy.prototype.addListener;
Bind an event, but put the callback into head of all callbacks.
EventProxy.prototype.headbind = function (ev, callback) {
debug('Add listener for %s', ev);
this._callbacks[ev] = this._callbacks[ev] || [];
this._callbacks[ev].unshift(callback);
return this;
};
Remove one or many callbacks.
callback
is null, removes all callbacks for the event.eventname
is null, removes all bound callbacks for all events.EventProxy.prototype.removeListener = function (eventname, callback) {
var calls = this._callbacks;
if (!eventname) {
debug('Remove all listeners');
this._callbacks = {};
} else {
if (!callback) {
debug('Remove all listeners of %s', eventname);
calls[eventname] = [];
} else {
var list = calls[eventname];
if (list) {
var l = list.length;
for (var i = 0; i < l; i++) {
if (callback === list[i]) {
debug('Remove a listener of %s', eventname);
list[i] = null;
}
}
}
}
}
return this;
};
removeListener
alias, unbind
EventProxy.prototype.unbind = EventProxy.prototype.removeListener;
Remove all listeners. It equals unbind()
Just add this API for as same as Event.Emitter.
EventProxy.prototype.removeAllListeners = function (event) {
return this.unbind(event);
};
Bind the ALL_EVENT event
EventProxy.prototype.bindForAll = function (callback) {
this.bind(ALL_EVENT, callback);
};
Unbind the ALL_EVENT event
EventProxy.prototype.unbindForAll = function (callback) {
this.unbind(ALL_EVENT, callback);
};
Trigger an event, firing all bound callbacks. Callbacks are passed the
same arguments as trigger
is, apart from the event name.
Listening for "all"
passes the true event name as the first argument.
EventProxy.prototype.trigger = function (eventname, data) {
var list, ev, callback, args, i, l;
var both = 2;
var calls = this._callbacks;
debug('Emit event %s with data %j', eventname, data);
while (both--) {
ev = both ? eventname : ALL_EVENT;
list = calls[ev];
if (list) {
for (i = 0, l = list.length; i < l; i++) {
if (!(callback = list[i])) {
list.splice(i, 1);
i--;
l--;
} else {
args = both ? SLICE.call(arguments, 1) : arguments;
callback.apply(this, args);
}
}
}
}
return this;
};
trigger
alias
EventProxy.prototype.emit = EventProxy.prototype.trigger;
trigger
alias
EventProxy.prototype.fire = EventProxy.prototype.trigger;
Bind an event like the bind method, but will remove the listener after it was fired.
EventProxy.prototype.once = function (ev, callback) {
var self = this;
var wrapper = function () {
callback.apply(self, arguments);
self.unbind(ev, wrapper);
};
this.bind(ev, wrapper);
return this;
};
var later = typeof process !== 'undefined' && process.nextTick || function (fn) {
setTimeout(fn, 0);
};
emitLater
make emit async
EventProxy.prototype.emitLater = function () {
var self = this;
var args = arguments;
later(function () {
self.trigger.apply(self, args);
});
};
Bind an event, and trigger it immediately.
EventProxy.prototype.immediate = function (ev, callback, data) {
this.bind(ev, callback);
this.trigger(ev, data);
return this;
};
immediate
alias
EventProxy.prototype.asap = EventProxy.prototype.immediate;
var _assign = function (eventname1, eventname2, cb, once) {
var proxy = this;
var argsLength = arguments.length;
var times = 0;
var flag = {};
Check the arguments length.
if (argsLength < 3) {
return this;
}
var events = SLICE.call(arguments, 0, -2);
var callback = arguments[argsLength - 2];
var isOnce = arguments[argsLength - 1];
Check the callback type.
if (typeof callback !== "function") {
return this;
}
debug('Assign listener for events %j, once is %s', events, !!isOnce);
var bind = function (key) {
var method = isOnce ? "once" : "bind";
proxy[method](key, function (data) {
proxy._fired[key] = proxy._fired[key] || {};
proxy._fired[key].data = data;
if (!flag[key]) {
flag[key] = true;
times++;
}
});
};
var length = events.length;
for (var index = 0; index < length; index++) {
bind(events[index]);
}
var _all = function (event) {
if (times < length) {
return;
}
if (!flag[event]) {
return;
}
var data = [];
for (var index = 0; index < length; index++) {
data.push(proxy._fired[events[index]].data);
}
if (isOnce) {
proxy.unbindForAll(_all);
}
debug('Events %j all emited with data %j', events, data);
callback.apply(null, data);
};
proxy.bindForAll(_all);
};
Assign some events, after all events were fired, the callback will be executed once.
Examples:
proxy.all(ev1, ev2, callback);
proxy.all([ev1, ev2], callback);
proxy.all(ev1, [ev2, ev3], callback);
EventProxy.prototype.all = function (eventname1, eventname2, callback) {
var args = CONCAT.apply([], arguments);
args.push(true);
_assign.apply(this, args);
return this;
};
all
alias
EventProxy.prototype.assign = EventProxy.prototype.all;
Assign the only one 'error' event handler.
EventProxy.prototype.fail = function (callback) {
var that = this;
that.once('error', function (err) {
that.unbind();
put all arguments to the error handler
fail(function(err, args1, args2, ...){})
callback.apply(null, arguments);
});
return this;
};
Assign some events, after all events were fired, the callback will be executed first time.
Then any event that predefined be fired again, the callback will executed with the newest data.
Examples:
proxy.tail(ev1, ev2, callback);
proxy.tail([ev1, ev2], callback);
proxy.tail(ev1, [ev2, ev3], callback);
EventProxy.prototype.tail = function () {
var args = CONCAT.apply([], arguments);
args.push(false);
_assign.apply(this, args);
return this;
};
tail
alias, assignAll
EventProxy.prototype.assignAll = EventProxy.prototype.tail;
tail
alias, assignAlways
EventProxy.prototype.assignAlways = EventProxy.prototype.tail;
The callback will be executed after the event be fired N times.
EventProxy.prototype.after = function (eventname, times, callback) {
if (times === 0) {
callback.call(null, []);
return this;
}
var proxy = this,
firedData = [];
this._after = this._after || {};
var group = eventname + '_group';
this._after[group] = {
index: 0,
results: []
};
debug('After emit %s times, event %s\'s listenner will execute', times, eventname);
var all = function (name, data) {
if (name === eventname) {
times--;
firedData.push(data);
if (times < 1) {
debug('Event %s was emit %s, and execute the listenner', eventname, times);
proxy.unbindForAll(all);
callback.apply(null, [firedData]);
}
}
if (name === group) {
times--;
proxy._after[group].results[data.index] = data.result;
if (times < 1) {
debug('Event %s was emit %s, and execute the listenner', eventname, times);
proxy.unbindForAll(all);
callback.call(null, proxy._after[group].results);
}
}
};
proxy.bindForAll(all);
return this;
};
The after
method's helper. Use it will return ordered results.
If you need manipulate result, you need callback
Examples:
var ep = new EventProxy();
ep.after('file', files.length, function (list) {
// Ordered results
});
for (var i = 0; i < files.length; i++) {
fs.readFile(files[i], 'utf-8', ep.group('file'));
}
EventProxy.prototype.group = function (eventname, callback) {
var that = this;
var group = eventname + '_group';
var index = that._after[group].index;
that._after[group].index++;
return function (err, data) {
if (err) {
put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
that.emit(group, {
index: index,
callback(err, args1, args2, ...)
result: callback ? callback.apply(null, SLICE.call(arguments, 1)) : data
});
};
};
The callback will be executed after any registered event was fired. It only executed once.
EventProxy.prototype.any = function () {
var proxy = this,
callback = arguments[arguments.length - 1],
events = SLICE.call(arguments, 0, -1),
_eventname = events.join("_");
debug('Add listenner for Any of events %j emit', events);
proxy.once(_eventname, callback);
var _bind = function (key) {
proxy.bind(key, function (data) {
debug('One of events %j emited, execute the listenner');
proxy.trigger(_eventname, {"data": data, eventname: key});
});
};
for (var index = 0; index < events.length; index++) {
_bind(events[index]);
}
};
The callback will be executed when the event name not equals with assigned event.
EventProxy.prototype.not = function (eventname, callback) {
var proxy = this;
debug('Add listenner for not event %s', eventname);
proxy.bindForAll(function (name, data) {
if (name !== eventname) {
debug('listenner execute of event %s emit, but not event %s.', name, eventname);
callback(data);
}
});
};
Success callback wrapper, will handler err for you.
fs.readFile('foo.txt', ep.done('content'));
// equal to =>
fs.readFile('foo.txt', function (err, content) {
if (err) {
return ep.emit('error', err);
}
ep.emit('content', content);
});
fs.readFile('foo.txt', ep.done('content', function (content) {
return content.trim();
}));
// equal to =>
fs.readFile('foo.txt', function (err, content) {
if (err) {
return ep.emit('error', err);
}
ep.emit('content', content.trim());
});
EventProxy.prototype.done = function (handler, callback) {
var that = this;
return function (err, data) {
if (err) {
put all arguments to the error handler
return that.emit.apply(that, ['error'].concat(SLICE.call(arguments)));
}
callback(err, args1, args2, ...)
var args = SLICE.call(arguments, 1);
if (typeof handler === 'string') {
getAsync(query, ep.done('query'));
or
getAsync(query, ep.done('query', function (data) {
return data.trim();
}));
if (callback) {
only replace the args when it really return a result
return that.emit(handler, callback.apply(null, args));
} else {
put all arguments to the done handler
//ep.done('some');
//ep.on('some', function(args1, args2, ...){});
return that.emit.apply(that, [handler].concat(args));
}
}
speed improve for mostly case: callback(err, data)
if (arguments.length <= 2) {
return handler(data);
}
callback(err, args1, args2, ...)
handler.apply(null, args);
};
};
make done async
EventProxy.prototype.doneLater = function (handler, callback) {
var _doneHandler = this.done(handler, callback);
return function (err, data) {
var args = arguments;
later(function () {
_doneHandler.apply(null, args);
});
};
};
Create a new EventProxy
Examples:
var ep = EventProxy.create();
ep.assign('user', 'articles', function(user, articles) {
// do something...
});
// or one line ways: Create EventProxy and Assign
var ep = EventProxy.create('user', 'articles', function(user, articles) {
// do something...
});
EventProxy.create = function () {
var ep = new EventProxy();
var args = CONCAT.apply([], arguments);
if (args.length) {
var errorHandler = args[args.length - 1];
var callback = args[args.length - 2];
if (typeof errorHandler === 'function' && typeof callback === 'function') {
args.pop();
ep.fail(errorHandler);
}
ep.assign.apply(ep, args);
}
return ep;
};
Backwards compatibility
EventProxy.EventProxy = EventProxy;
return EventProxy;
});