I want to insert on pre execute and post execute hooks on functions in javascript classes.
Lets say I have a class like this.
class Foo {
method1(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
method2(p3) {
this.p3 = p3;
}
}
I want to define a before and after hook for these preexisting class methods. Something like this.
class Foo {
before(funName, ...params){
// Should print ('method1', [p1, p2]) when method 1 is called
// and ('method2', [p3]) when method 2 is called
console.log(funName, params)
}
after(funName, result){
// Should print the function name followed by its result
console.log(funName, result)
}
method1(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
method2(p3) {
this.p3 = p3;
}
}
export default Foo;
What is the best way of implementing these hooks with minimal changes in existing code?
I want to insert on pre execute and post execute hooks on functions in javascript classes.
Lets say I have a class like this.
class Foo {
method1(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
method2(p3) {
this.p3 = p3;
}
}
I want to define a before and after hook for these preexisting class methods. Something like this.
class Foo {
before(funName, ...params){
// Should print ('method1', [p1, p2]) when method 1 is called
// and ('method2', [p3]) when method 2 is called
console.log(funName, params)
}
after(funName, result){
// Should print the function name followed by its result
console.log(funName, result)
}
method1(p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
method2(p3) {
this.p3 = p3;
}
}
export default Foo;
What is the best way of implementing these hooks with minimal changes in existing code?
Share Improve this question asked Feb 23, 2020 at 15:01 Souradeep NandaSouradeep Nanda 3,2982 gold badges34 silver badges48 bronze badges2 Answers
Reset to default 5Here is a rough solution to the problem:
// we iterate over all method names
Object.getOwnPropertyNames(Foo.prototype).forEach((name) => {
// First to do: we save the original method. Adding it to prototype
// is a good idea, we keep 'method1' as '_method1' and so on
Foo.prototype['_' + name] = Foo.prototype[name];
// Next, we replace the original method with one that does the logging
// before and after method execution.
Foo.prototype[name] = function() {
// all arguments that the method receives are in the 'arguments' object
console.log(`Method call: method1(${Object.values(arguments).join(', ')})`);
// now we call the original method, _method1, on this with all arguments we received
// this is probably the most confusing line of code here ;)
// (I never user this['method'] before - but it works)
const result = this['_' + name](...arguments);
// here is the post-execution logging
console.log(`Method result: ${result}`);
// and we need to return the original result of the method
return result;
};
});
Please note that this code is not part of the class itself, execute it as a normal script.
And there is a good chance that this short proof of concept crashes on real-world classes and requires some additional checks and special-case handlers, especially to get proper logging output. But it works with you Foo class.
Here's the working example: https://codesandbox.io/s/great-fog-c803c
Proxy solution
class Foo {
classAlias = false;
proxyHandle = {
// little hack where we save refrenece to our class inside the object
main : this,
/**
* The apply will be fired each time the function is called
* @param target Called function
* @param scope Scope from where function was called
* @param args Arguments passed to function
* @return Results of the function
*/
apply : function (target, scope, args) {
const func_name = target.name;
console.log('before', func_name);
// let's call some method of this class to actually check if this is the right instance
// also remember that you have to exclude methods which you are gonna use
// inside here to avoid “too much recursion” error
this.main._free.instanceCheck();
// here we bind method with our class by accessing reference to instance
const results = target.bind(this.main)(...args);
console.log('after', func_name, results);
return results;
}
}
constructor(classAlias) {
// Get all methods of choosen class
let methods = Object.getOwnPropertyNames( Foo.prototype );
// Find and remove constructor as we don't need Proxy on it
let consIndex = methods.indexOf('constructor');
if ( consIndex > -1 ) methods.splice(consIndex, 1);
// Replace all methods with Proxy methods
methods.forEach( methodName => {
this[methodName] = new Proxy( this[methodName], this.proxyHandle );
});
this.classAlias = classAlias;
}
// easies trick to do to avoid infinite loop inside apply is to set your functions
// inside object, as getOwnPropertyNames from prototype will only get methods
_free = {
instanceCheck : () => {
// this will check if this is our Foo class
console.log(this.classAlias);
}
}
log() {
return 'Result';
}
}
(new Foo('Proxy Class A')).log();
/*
Output:
before log
Proxy Class A
after log Result
*/
Just wanted to share because I've read in ments that someone had problem to set Proxy. You can read more about Proxies here, and here more about apply.
Remember that in proxy handle, the this
is actually this.main
. For better understanding, you can change it to classInstance
or something similar.