11

If I have an arbitrary function myFunc, what I'm aiming to do is replace this function with a wrapped call that runs code before and after it executes, e.g.

// note: psuedo-javascript

var beforeExecute = function() { ... }
var afterExecute = function() { ... }

myFunc = wrap(myFunc, beforeExecute, afterExecute);

However, I don't have an implementation of the required wrap function. Is there anything that already exists in jQuery like this (I've had a good look through the docs but cannot see anything)? Alternatively does anybody know of a good implementation of this because I suspect that there are a bunch of edge cases that I'll miss if I try to write it myself?

(BTW - the reason for this is to do some automatic instrumentation of functions because we do a lot of work on closed devices where Javascript profilers etc. are not available. If there's a better way than this then I'd appreciate answers along those lines too.)

Greg Beech
  • 133,383
  • 43
  • 204
  • 250

5 Answers5

13

Here’s a wrap function which will call the before and after functions with the exact same arguments and, if supplied, the same value for this:

var wrap = function (functionToWrap, before, after, thisObject) {
    return function () {
        var args = Array.prototype.slice.call(arguments),
            result;
        if (before) before.apply(thisObject || this, args);
        result = functionToWrap.apply(thisObject || this, args);
        if (after) after.apply(thisObject || this, args);
        return result;
    };
};

myFunc = wrap(myFunc, beforeExecute, afterExecute);
Martijn
  • 13,225
  • 3
  • 48
  • 58
  • I'm going to tick this one as the correct answer, because I think it's the most complete solution to the problem and handles functions with an arbitrary number of arguments. Cheers! Was hoping there might be a standard library function in jQuery for this, but I can always add it to our extensions. – Greg Beech Mar 10 '11 at 15:54
  • I’ve made a tiny edit, so that the 'regular' `this` object is used if no `thisObject` was specified when the wrapper was created. – Martijn Mar 11 '11 at 08:14
4

The accepted implementation does not provide an option to call wrapped (original) function conditionally.

Here is a better way to wrap and unwrap a method:

  /*
    Replaces sMethodName method of oContext with a function which calls the wrapper 
    with it's list of parameters prepended by a reference to wrapped (original) function.

    This provides convenience of allowing conditional calls of the 
    original function  within the wrapper,

    unlike a common implementation that supplies "before" and "after" 
    cross cutting concerns as two separate methods.

    wrap() stores a reference to original (unwrapped) function for 
    subsequent unwrap() calls.

    Example: 
    =========================================

    var o = {
        test: function(sText) { return sText; }
    }

    wrap('test', o, function(fOriginal, sText) {
        return 'before ' + fOriginal(sText) + ' after';
    });
    o.test('mytext')  // returns: "before mytext after"

    unwrap('test', o);
    o.test('mytext')  // returns: "mytext"

    =========================================
*/
function wrap(sMethodName, oContext, fWrapper, oWrapperContext) {
    var fOriginal = oContext[sMethodName];

    oContext[sMethodName] = function () {
        var a = Array.prototype.slice.call(arguments);
        a.unshift(fOriginal.bind(oContext));
        return fWrapper.apply(oWrapperContext || oContext, a);
    };
    oContext[sMethodName].unwrapped = fOriginal;
};

/*
    Reverts method sMethodName of oContext to reference original function, 
    the way it was before wrap() call
*/
function unwrap(sMethodName, oContext) {
    if (typeof oContext[sMethodName] == 'function') {
        oContext[sMethodName] = oContext[sMethodName].unwrapped;
    }
};
Lex Podgorny
  • 2,598
  • 1
  • 23
  • 40
3

This is the example I would use

<script type="text/javascript">
  var before = function(){alert("before")};
  var after = function(param){alert(param)};
  var wrap = function(func, wrap_before, wrap_after){
    wrap_before.call();
    func.call();
    wrap_after.call();
  };
  wrap(function(){alert("in the middle");},before,function(){after("after")});
</script>
m4risU
  • 1,231
  • 11
  • 14
  • It shows as well how to parametrize functions you are running with the wrap. If you call fuctions just like that with parameter whey may be executed before wrap itself. – m4risU Mar 10 '11 at 11:23
  • This doesn't seem to work if the function being wrapped has parameters...? – Greg Beech Mar 10 '11 at 11:40
  • That is why you should wrap the parameter function in function(){...} call. Otherwise You will end up firing the function to get its result. – m4risU Mar 10 '11 at 11:41
2

You could do something like:

var wrap = function(func, pre, post)
{
  return function()
  {
    var callee = arguments.callee;
    var args = arguments;

    pre();    
    func.apply(callee, args);    
    post();
  };
};

This would allow you to do:

var someFunc = function(arg1, arg2)
{
    console.log(arg1);
    console.log(arg2);
};

someFunc = wrap(
    someFunc,
    function() { console.log("pre"); },
    function() { console.log("post"); });

someFunc("Hello", 27);

Which gives me an output in Firebug of:

pre
Hello
27
post

The important part when wrapping this way, is passing your arguments from the new function back to the original function.

Matthew Abbott
  • 60,571
  • 9
  • 104
  • 129
-1

Maybe I'm wrong, but I think you can directly create an anonym function and assign it to myFunc:

myFunc = function(){
             BeforeFunction();
             myFunc();
             AfterFunction();
         }

In this way you can control the arguments of every function.

Terseus
  • 2,082
  • 15
  • 22