How do I wrap a function in Javascript?
Asked Answered
F

6

41

I'm writing a global error handling "module" for one of my applications.

One of the features I want to have is to be able to easily wrap a function with a try{} catch{} block, so that all calls to that function will automatically have the error handling code that'll call my global logging method. (To avoid polluting the code everywhere with try/catch blocks).

This is, however, slightly beyond my understanding of the low-level functioning of JavaScript, the .call and .apply methods, and the this keyword.

I wrote this code, based on Prototype's Function.wrap method:

Object.extend(Function.prototype, {
  TryCatchWrap: function() {
    var __method = this;
    return function() {
            try { __method.apply(this, arguments) } catch(ex) { ErrorHandler.Exception(ex); }
    }
  }
});

Which is used like this:

function DoSomething(a, b, c, d) {
    document.write(a + b + c)
    alert(1/e);
}

var fn2 = DoSomething.TryCatchWrap();
fn2(1, 2, 3, 4);

That code works perfectly. It prints out 6, and then calls my global error handler.

My question is: will this break something when the function I'm wrapping is within an object, and it uses the "this" operator? I'm slightly worried since I'm calling .apply, passing something there, I'm afraid this may break something.

Flamboyant answered 28/11, 2008 at 20:26 Comment(0)
A
71

Personally instead of polluting builtin objects I would go with a decorator technique:

var makeSafe = function(fn){
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

You can use it like that:

function fnOriginal(a){
  console.log(1/a);
};

var fn2 = makeSafe(fnOriginal);
fn2(1);
fn2(0);
fn2("abracadabra!");

var obj = {
  method1: function(x){ /* do something */ },
  method2: function(x){ /* do something */ }
};

obj.safeMethod1 = makeSafe(obj.method1);
obj.method1(42);     // the original method
obj.safeMethod1(42); // the "safe" method

// let's override a method completely
obj.method2 = makeSafe(obj.method2);

But if you do feel like modifying prototypes, you can write it like that:

Function.prototype.TryCatchWrap = function(){
  var fn = this; // because we call it on the function itself
  // let's copy the rest from makeSafe()
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

Obvious improvement will be to parameterize makeSafe() so you can specify what function to call in the catch block.

Amyl answered 28/11, 2008 at 21:13 Comment(3)
OK, so besides the preference of polluting or not... Your last code snippet looks identical to mine. Should I understand from this that my code does work with objects, and the "this" keyword? Thanks!Flamboyant
Yes: you pass both "this" and original arguments to the wrapped method. But you don't return its result, making wrapping incomplete. But it doesn't matter if you wrap a function that doesn't return a value.Amyl
I dont know that I would call this method "makeSafe." That really gives a false impression that consuming exceptions is "safe".Cockchafer
J
12

2017 answer: just use ES6. Given the following demo function:

function doThing(){
  console.log(...arguments)
}

You can make your own wrapper function without needing external libraries:


function wrap(someFunction){
  function wrappedFunction(){
    var newArguments = [...arguments]
    newArguments.push('SECRET EXTRA ARG ADDED BY WRAPPER!')
    console.log(`You're about to run a function with these arguments: \n  ${newArguments}`)
    return someFunction(...newArguments)
  }
  return wrappedFunction
}

In use:

doThing('one', 'two', 'three')

Works as normal.

But using the new wrapped function:

const wrappedDoThing = wrap(doThing)
wrappedDoThing('one', 'two', 'three')

Returns:

one two three SECRET EXTRA ARG ADDED BY WRAPPER!

2016 answer: use the wrap module:

In the example below I'm wrapping process.exit(), but this works happily with any other function (including browser JS too).

var wrap = require('lodash.wrap');

var log = console.log.bind(console)

var RESTART_FLUSH_DELAY = 3 * 1000

process.exit = wrap(process.exit, function(originalFunction) {
    log('Waiting', RESTART_FLUSH_DELAY, 'for buffers to flush before restarting')
    setTimeout(originalFunction, RESTART_FLUSH_DELAY)
});

process.exit(1);
Juttajutty answered 17/5, 2016 at 12:11 Comment(2)
Why are you splicing the args?Melliemelliferous
@Melliemelliferous not sure, I just went through an updated my whole answer and got rid of the splicing.Juttajutty
H
3

Here is an ES6 style:

const fnOriginal = (a, b, c, d) => {
    console.log(a);
    console.log(b);
    console.log(c);
    console.log(d);
    return 'Return value from fnOriginal';
};


const wrapperFunction = fn => {
    return function () {
        try {
            const returnValuFromOriginal = fn.apply(this, arguments);
            console.log('Adding a new line from Wrapper :', returnValuFromOriginal);
        } catch (ex) {
            ErrorHandler.Exception(ex);
        }
    };
};

const fnWrapped = wrapperFunction(fnOriginal);
fnWrapped(1, 2, 3, 4);
Hugely answered 6/5, 2021 at 6:47 Comment(0)
N
2

Object.extend(Function.prototype, { Object.extend in the Google Chrome Console gives me 'undefined' Well here's some working example:

    Boolean.prototype.XOR =
//  ^- Note that it's a captial 'B' and so
//      you'll work on the Class and not the >b<oolean object
        function( bool2 ) { 

           var bool1 = this.valueOf();
           //         'this' refers to the actual object - and not to 'XOR'

           return (bool1 == true   &&   bool2 == false)
               || (bool1 == false   &&   bool2 == true);
        } 

alert ( "true.XOR( false ) => " true.XOR( false ) );

so instead of Object.extend(Function.prototype, {...}) Do it like: Function.prototype.extend = {}

Neonatal answered 28/10, 2012 at 15:25 Comment(0)
G
1

Function wrapping in good old fashion:

//Our function
function myFunction() {
  //For example we do this:
  document.getElementById('demo').innerHTML = Date();
  return;
}

//Our wrapper - middleware
function wrapper(fn) {
  try {
    return function(){
      console.info('We add something else', Date());
      return fn();
    }
  }
  catch (error) {
    console.info('The error: ', error);
  }
}

//We use wrapper - middleware
myFunction = wrapper(myFunction);

The same in ES6 style:

//Our function
let myFunction = () => {
  //For example we do this:
  document.getElementById('demo').innerHTML = Date();
  return;
}

//Our wrapper - middleware
const wrapper = func => {
  try {
    return () => {
      console.info('We add something else', Date());
      return func();
    }
  }
  catch (error) {
    console.info('The error: ', error);
  }
}

//We use wrapper - middleware
myFunction = wrapper(myFunction);
Gluey answered 5/5, 2019 at 3:44 Comment(1)
how will you handle arguments passed in your wrapped function?Aboveground
L
0

The following wrapping utility takes a function and enables the developer to inject a code or wrap the original:


function wrap(originalFunction, { inject, wrapper } = {}) {

    const wrapperFn = function(...args) {
        if (typeof inject === 'function') {
            inject(originalFunction, this);
        }
        if (typeof wrapper === 'function') {
            return wrapper(originalFunction, this, args);
        }
        return originalFunction.apply(this, args);
    };

    // copy the original function's props onto the wrapper
    for(const prop in originalFunction) {
      if (originalFunction.hasOwnProperty(prop)) {
        wrapperFn[prop] = originalFunction[prop];
      }
    }
    return wrapperFn;
}

Usage example:


// create window.a()
(function() {

    const txt = 'correctly'; // outer scope variable
    
    window.a = function a(someText) { // our target
        if (someText === "isn't") {
            throw('omg');
        }
        return ['a', someText, window.a.c, txt].join(' ');
    };
    
    window.a.c = 'called'; // a.c property example
})();

const originalFunc = window.a;
console.log(originalFunc('is')); // logs "a is called correctly"

window.a = wrap(originalFunc);
console.log(a('is')); // logs "a is called correctly"

window.a = wrap(originalFunc, { inject(func, thisArg) { console.log('injected function'); }});
console.log(a('is')); // logs "injected function\na is called correctly"

window.a = wrap(originalFunc, { wrapper(func, thisArg, args) { console.log(`doing something else instead of ${func.name}(${args.join(', ')})`); }});
console.log(a('is')); // logs "doing something else instead of a(is)"

window.a = wrap(originalFunc, {
    wrapper(func, thisArg, args) {
        try {
            return func.apply(thisArg, args);
        } catch(err) {
            console.error('got an exception');
        }
    }
});
a("isn't"); // error message: "got an exception"

The last example demonstrates how to wrap your function with a try-catch clause

La answered 9/12, 2020 at 13:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.