Wrap javascript class methods with callback handler
Helper to wrap the methods of a class so that it can perform external operations around the functions of the instance to handle errors, store results, maintain a call stack, chain calls, etc.
by Fernando Testa
January 10, 2023
HTML
<html>
<body>
<div id="app">
<div class="block">
<b>Output</b>
<pre id="output">
</pre>
</div>
<div>
<h3>
Help
</h3>
Wrap methods of a class in a way you can perform outter operations around the object functions to handle errors, store results, callstack, etc.
</div>
</div>
</body>
</html>
CSS
body {
background: #20262E;
padding: 5px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 10px;
transition: all 0.2s;
font-size: 13px;
font-family: Helvetica;
}
pre {
margin: 0px 0px 0px 0px !important;
}
p {
margin-top: 0px;
padding-top: 0px;
}
.block {
display: inline-block;
width: 49%;
}
JavaScript
const callbackHandler = function(prop, obj, args) {
var r
try {
console.log("Callback intercepted - Before")
r = Reflect.apply(prop, obj, args)
} catch (error) {
console.log("error handled")
console.error(error)
}
console.log("Callback intercepted - After")
return r
}
function output(t) {
document.getElementById("output").innerHTML +=
("<p>" + JSON.stringify(t, null, 2) + "</p>")
}
function getClassMethods(instance, ignore = ['constructor']) {
// Modified source: https://stackoverflow.com/a/31055217/1372973
if (!instance) return []
if (instance.__methods) return instance.__methods
const props = [];
let obj = instance;
do {
if (obj.constructor.name == "Object") continue;
var m = Object.getOwnPropertyNames(obj)
props.push(...m);
} while ((obj = Object.getPrototypeOf(obj)) && obj);
let r = props.sort().filter((method, i, arr) => {
if (ignore.indexOf(method) !== -1) return false;
if (method != arr[i + 1] && typeof instance[method] == 'function') return true;
});
instance.__methods = r
return r
}
// source and target must be instances, not prototypes
function cloneMethods(source, target) {
var sourceMethods = getClassMethods(source)
let t = target.__proto__
sourceMethods.forEach(function(method) {
let sMethod = source.__proto__[method]
if (sMethod) {
t[method] = sMethod.bind(source)
}
})
}
const wrapClassMethods = function(obj) {
const classMethods = getClassMethods(obj)
classMethods.forEach(methodName => {
const methodFunc = obj[methodName]
console.log('wrapped method: ' + methodName)
obj[methodName] = (...args) => {
return callbackHandler(methodFunc, obj, args)
}
})
console.log('--------')
}
class MyParent {
constructor() {
return wrapClassMethods(this)
}
test(val = "123") {
let r = "Testing parent: " + val
console.log(r)
return r
}
}
class MySon extends MyParent {
testSon(val = "678") {
...