- has that function been invoked ?
- has that function received the expected context ?
- which argument has been passed to that function ?
- what was the output of the function ?
Update thanks to @bga_ hint about the output property in after notification, it made perfect sense
The Concept
For fun and no profit I have created a prototype which aim is to bring a DOM like interface to any sort of function or method in order to monitor its lifecycle:- the "before" event, able to preventDefault() and avoid the original function call at all
- the "after" event, in order to understand if the function did those expected changes to the environment or to a generic input object, or simply to analyze the output of the previous call
- the "error" event, in case we want to be notified if something went wrong during function execution
- the "handlererror" event, just in case we are the cause of an error while we are monitoring the original function
Basic Example
var nFromCharcode = String.fromCharCode.notifier({
before: function (e) {
if (e.arguments.length > 2048) {
throw "too many arguments";
e.preventDefault(); // won't even try to execute it
}
// in case you want to remove this listener ...
e.notifier.removeListener("before", e.handler);
},
after: function (e) {
if (e.output !== "PQR") {
throw "expected PQR got " + e.output + " instead";
}
},
handlererror: function (e) {
testFramework.failBecause("" + e.error);
}
});
// run the test ...
nFromCharcode(80, 81, 82); // "PQR"
nFromCharcode.apply(null, arrayOf2049Codes); // testFramework will fail
The notifier itself is a function, precisely the original function wrapper with enriched API in order to monitor almost every aspect of a method or a function.
The event object passed through each listener has these properties:
- notifier: the object create to monitor the function and notify all listeners
- handler: the current handler to make the notifier remove listener easier
- callback: the original function that has been wrapped by the notifier
- type: the event type such before, error, after, handlererror
- arguments: passed arguments transformed already into array
- context: the "this" reference used as callback context
- error: the optional error object for events error and handlererror
- preventDefault: the method able to avoid function execution if called in the before listener
- output: assigned only during "after" notification and if no error occurred, handy to compare expected results
I guess there is really nothing else we could possibly know about a notifier, and its callback, lifecycle, what do you think?
The Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Function.prototype.notifier = (function () {"use strict"; | |
// (C) WebReflection - Mit Style License | |
function create(callback) { | |
function notifier() { | |
var args = [].slice.call(arguments), output; | |
if (fire(notifier, "before", callback, this, args, null)) { | |
try { | |
output = callback.apply(this, args); | |
} catch(e) { | |
fire(notifier, "error", callback, this, args, e); | |
} | |
fire(notifier, "after", callback, this, args, output); | |
return output; | |
} | |
} | |
notifier._callback = callback; | |
notifier._handlers = {}; | |
notifier.addListener = addListener; | |
notifier.fireListener = fireListener; | |
notifier.removeListener = removeListener; | |
return notifier; | |
} | |
function addListener(type, handler) { | |
if (hasOwnProperty.call(this._handlers, type)) { | |
i = indexOf.call(this._handlers[type], handler); | |
if (i < 0) { | |
this._handlers[type].push(handler); | |
} | |
} else { | |
this._handlers[type] = [handler]; | |
} | |
} | |
function fireListener(type, context, args) { | |
fire(this, type, this._callback, context, args, null); | |
} | |
function removeListener(type, handler) { | |
if (hasOwnProperty.call(this._handlers, type)) { | |
i = indexOf.call(this._handlers[type], handler); | |
if (~i) { | |
this._handlers[type].splice(i, 1); | |
if (!this._handlers[type].length) { | |
delete this._handlers[type]; | |
} | |
} | |
} | |
} | |
function fire(notifier, type, callback, context, args, error) { | |
if (hasOwnProperty.call(notifier._handlers, type)) { | |
for (var | |
_handlers = notifier._handlers[type].slice(), | |
i = 0, length = _handlers.length, | |
extra = type == "after" ? "output" : "error", | |
e, result; | |
i < length; i++ | |
) { | |
e = { | |
notifier: notifier, | |
handler: _handlers[i], | |
callback: callback, | |
type: type, | |
arguments: args, | |
context: context, | |
preventDefault: preventDefault | |
}; | |
e[extra] = error; | |
try { | |
if (typeof _handlers[i] == "function") { | |
_handlers[i].call(callback, e); | |
} else { | |
_handlers[i].handleEvent(e); | |
} | |
result = result || e.dont; | |
} catch(e) { | |
fire( | |
notifier, | |
"handlererror", | |
callback, | |
context, | |
args, | |
e | |
); | |
} | |
} | |
return !result; | |
} | |
return true; | |
} | |
function preventDefault() { | |
this.dont = true; | |
} | |
var | |
indexOf = [].indexOf || function indexOf(that) { | |
for(i = this.length; i-- && this[i]!== that; ); | |
return i; | |
}, | |
hasOwnProperty = {}.hasOwnProperty, | |
NOTIFIER = "_notifier", | |
i | |
; | |
return function notifier(object) { | |
var | |
self = this, | |
notifier = hasOwnProperty.call(self, NOTIFIER) ? | |
self[NOTIFIER] : | |
self[NOTIFIER] = create(self) | |
, | |
key | |
; | |
for (key in object) { | |
if (hasOwnProperty.call(object, key)) { | |
addListener.call(notifier, key, object[key]); | |
} | |
} | |
return notifier; | |
}; | |
}()); |
As Summary
I have also a full test coverage for this notifier and I hope someone will use it and will come back to provide some feedback, cheers!
No comments:
Post a Comment