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

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") {
   ...