Tuesday, November 08, 2011

Function.prototype.notifier

There are way too many ways to stub functions or methods, but at the end of the day all we want to know is always the same:
  • 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
The reason I have chosen an addEventListener like interface, called in this case addListener, is simple: JavaScript works pretty well with event driven applications so what else could be better than an event driven approach?

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




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!

Labels: , , , , ,

9 Comments:

Blogger zzo said...

love it! would be nice to time the functions perhaps and keep the same function name so code can call the notifie'd function w/o change... ??
great stuff!

09 November, 2011 00:27  
Blogger Andrea Giammarchi said...

to time the function ? ... I am not sure I get it ... would you time DOM listeners? You can pass the async callback you expect and let the framework take care of the time and of course you may do something like this:

String.fromCharCode = String.fromCharCode.notifier();

and whenever you want ...

String.fromCharCode = String.fromCharCode._callback;

looks dirty, but works ;)

09 November, 2011 00:37  
Blogger Darktalker said...

nice concept, it reminds me of aspect pattern.
it's always entertaining of discovering your code :)

09 November, 2011 11:17  
Blogger Paul Irish said...

on the topic of AOPing all methods like this... maybe i'm not reading your code correctly.. but looking here and at http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/
.. i'm wondering..

does executing the func method like `func()` always mean that `func.call` is executed? in all browsers?

09 November, 2011 19:03  
Blogger Andrea Giammarchi said...

Paul .. I am not sure I got your question ... so, a notifier *is* a function, you can call the notifier directly()

That invoke means the "this", whatever it is, will be used as a context, where in ES5 and "use strict" directive the context could be undefined so that undefined will be the fun.call(this) inside the notifier.

Once again, Function.prototype.notifier is a function, not a generic object, the way you invoke any sort of function, included those bound, will eb reflected in reality.

DidI answer any of your doubts ?

09 November, 2011 20:41  
Blogger Andrea Giammarchi said...

P.S. the link you posted tells me database connection error

09 November, 2011 20:42  
Blogger Andrea Giammarchi said...

Paul, as "indirect effect", I have realized if no "before" listener where there nothing was happening ... in any case, I have updated the code and now I can test that this:


(function () {"use strict";
function test() {
alert(this == null);
}
test.notifier()();
}());

produce true, so that this is preserved accordingly with where the function has been created ( inside use strict, or outside )

09 November, 2011 21:00  
Blogger Andrea Giammarchi said...

as ulterior proof:


(function () {"use strict";
function test() {
alert(this == null);
}
return test;
}()).notifier()();


still true

09 November, 2011 21:01  
Blogger Andrea Giammarchi said...

so this will fail:

(function () {"use strict";
function test() {
alert(this == null);
}
return test;
}()).notifier().call(this);


is that what you asked for ?
'cause in that case you were expecting a false, invoking the function via global/window object as this ... isn't it ?

09 November, 2011 21:31  

Post a Comment

Links to this post:

Create a Link

<< Home