Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Function Watcher in JS

I'm working on a pet project, a little front-end library for students. It reads variables/code in a JS file and tests it, outputting some panels. The code itself roughly follows the Jest framework.

My problem is that I'm trying to create a function that watches the execution of other functions, counts them, and lets me access the count.

function watchFunction(funcName){
    let originalFunction = window[funcName];
    let counter = 0;
    
    // Wrap the function, counts when called
    window[funcName] = function(...args){
        console.log("watching");
        counter++;
        return originalFunction(...args);
    }

    return {
        getCount: () => {return counter},
        reset: () => {
            // Unwrap the function
            window[funcName] = originalFunction
        }
    }
}

This seems to work for methods like Number() or parseInt(), but I don't know how I would go about accessing methods like Math.floor(), or prototype methods like Array.prototype.map().

I've tried passing in the function reference instead of using window["funcNameString"], but that doesn't seem to work.

Does anyone have suggestions or tips for wrapping functions or watching functions like this?

EDIT:

It appears a solution was found!

function watchFunction(obj, fName) {
  let counter = 0;

  const originalFunction = obj[fName];
  obj[fName] = (...args) => {
    counter++;
    return originalFunction.bind(obj)(...args);
  };

  return {
    removeWatcher: () => (obj[fName] = originalFunction),
    resetCount: () => (counter = 0),
    getCount: () => counter,
  };
}

Example of use:

// Array.prototype.push
const arrayPushWatcher = watchFunction(Array.prototype, "push");
let arr = [];

// 0
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);

// 1
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);

// 2
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arrayPushWatcher.removeWatcher();
arr.push(1);

// 2 (stopped counting)
console.log("Array.prototype.push", arrayPushWatcher.getCount());
like image 552
Zintis May-Krumins Avatar asked Aug 12 '26 16:08

Zintis May-Krumins


1 Answers

How to watch for any function call


Is that what you want? I can also write a block for this function so that it determines whether an object has been passed in or a string. If string -> run this function on window as a property "objectThatStoresFunction".

I've tried playing around with the Function.prototype, but it doesn't really work. So the function turned out a bit more complex.

This code below works both with functions / objects on window Array.prototype.map (Prototype / Class functions)

function watchFunction(objectThatStoresFunction, functionName) {
  let counter = 0;

  const originalFunction = objectThatStoresFunction[functionName];

  objectThatStoresFunction[functionName] = (...args) => {
    counter += 1;
    return originalFunction(...args);
  }

  return {
    getCount: () => {
      return counter
    }
  }
}
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log(mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log(mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log(mathRoundWatcher.getCount());

function watchFunction(objectThatStoresFunction, functionName, optionalOriginalFunction) {
const self = this;
  if (optionalOriginalFunction) {
    objectThatStoresFunction = this.window;
    functionName = optionalOriginalFunction.name;
  }
  let counter = 0;

  const originalFunction = objectThatStoresFunction[functionName] || optionalOriginalFunction;
  objectThatStoresFunction[functionName] = (...args) => {
    counter += 1;
    return originalFunction.bind(self)(...args);
  }
  return {
    // should it remove the watcher or reset the count?
    reset: () => objectThatStoresFunction[functionName] = originalFunction,
    getCount: () => {
return counter;
    }
  }
}
const arrayMapWatcher = watchFunction(Array.prototype, 'map');

// 0
console.log('Array.prototype.map', arrayMapWatcher.getCount());

[-99].map(() => {});

// 1
console.log('Array.prototype.map', arrayMapWatcher.getCount());

const mathRoundWatcher = watchFunction(Math, 'round');

// 0
console.log('Math.round', mathRoundWatcher.getCount());

// 1
Math.round(99666.9999999);
console.log('Math.round', mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log('Math.round', mathRoundWatcher.getCount());
const alertWatcher = watchFunction(null, null, window.alert);
// 0
console.log('window.alert', alertWatcher.getCount());
// 1
window.alert('1');
console.log('window.alert', alertWatcher.getCount());
// 2
alert('2')
console.log('window.alert', alertWatcher.getCount());
// reset the alertWatcher counter
alertWatcher.reset();

This code above breaks the stacksnippets.com when used with Array.prototype.map for some reason, please see this JsFiddle link:

https://jsfiddle.net/ctbjnawz/3/

like image 152
Aifos Si Prahs Avatar answered Aug 14 '26 06:08

Aifos Si Prahs