The journey into JavaScript function interception

Introduction

The decorator pattern is a great tool for adding additional behavior or functionality to objects and often is a good alternative to having created and maintaining many subclasses, especially when it comes to necessity to handle different combination of those new features at run time.

But there is even more…

In JavaScript where functions are first-class objects and therefore they can be returned as values from other functions we can use this pattern to decorate functions and methods too, as explained by Ross Harmes and Dustin Diaz in their terrific book "Pro JavaScript Design Patterns".

The idea behind this is to leverage "closures", the “apply” method of Function.prototype and the “arguments” object to create the actual function decorator and then use it to decorate function passed as argument.

Let’s see how we can use similar technique to solve real life programming challenge where knowledge and creativity may come to rescue.

We will use Mozilla Firebug Console to visually back up our observation.


Given

  1. The Big File with JavaScript code, which has many global scope variables and long tightly coupled functions which makes it very difficult to maintain.
  2. Big File is deployed on many not related to each other servers where its functionality has to be presented.
  3. Making changes to Big File is associated with an extreme risk of breaking some other functionality which perhaps depends on this piece of code and is costly in terms of time to be spent for analyzing, understanding and testing all possible outcomes.


Requirements

  1. Introduce self-contained SET of new complex capabilities to web page being serviced by Big File.
  2. The SET has to be deployed not on all hosting servers where Big File is present, but only on selected ones.


Constraints

  1. The SET‘s initialization code has to be run right after either one of two global scope long functions (from Big File) has finished its execution and created preconditions, required for our SET to be reinitialized.
  2. There is no budget to rewrite the Big File in accordance with best practices, test everything and redeploy it.


Analysis

The most obvious solution – to change both global scope functions by appending SET‘s initialisation code to the end of each one of them is by far not a best one, due to following reasons:

  1. We cannot change all instances of Big File on all servers with intention to maintain its version integrity across all locations (due to paragraph 2 of Requirements)
  2. Changing selected instances of Big File we will end up with having to maintain ever diverging versions of it.

Should we choose to go with second option anyway – we will have to load up the Big File even with more code for correct server instance identification logic which is concerned with a ceremony rather than an essence of fulfilling our requirements.


Decision

  1. We are going to create another JavaScript file (the Plug In) which will contain all code needed for SET to be functional and we will add reference to it in the Page where the Big File is referenced.
  2. The new JavaScript file will extend functionality and behaviour of Big File.
  3. The new JavaScript file will be deployed only on servers where its presence is required.


Solution

In order to implement our decision the new JavaScript file will have to establish following functionality

  1. Discover presence of two target functions in outer scope
  2. Intercept those functions using similar to decorator design pattern technique:
  • Execute intercepted functions’ own code
  • Execute SET‘s initialization code.
  • Return intercepted function execution result to its caller.


Implementation

The simplified versions of function to be intercepted and result of their executions are shown below.

The SET initialization function:

Let’s build our function’s Interceptor.

It might also be seen as the Interceptor’s maker, which will produce and return another function – the Interceptor itself that wraps the target function (decorates it with additional required functionality, described above).

The “FuncInterceptor” should receive function to be intercepted - “func” as argument and return a new anonymous function in place of it, which is going to serve as substitution for the “func” later on.

We will provide intercepting function with all parameters, required for her execution by means of “arguments” object, which has access to any parameters, to be passed to substitution (line 31).


Testing

The code for testing the process is shown below.

The testing code consists of three parts. In the first part we are testing initial functionality execution, which imitates how everything works without our intervention.

The second part imitates introduction of our ‘plug in’ file with its own code (omitted) where interception is taking place.

Finally the third part imitates functionality, augmented by interception, where two unaltered target functions, completely unaware of being “hijacked”, execute injected by interceptor code, required for SET to be initialized.

We can run the test code by clicking on “Run” button in the right section of the console as shown below.

As we can see our initialization cod has been executed twice at line 24 as a result of two independent calls to each of target functions made by their callers (lines 66 and 67).

Notice that the callers have gotten the results of the target functions they were calling as they should have expected!

This is all exactly what we have been looking for!


Benefits

  1. We did not touch the Big File, therefore did not add even more troubles to ones it already has. We can be quite sure that we did not break anything unintentionally.
  2. The servers where new functionality is required acquired it encapsulated in new separated file, while other servers may remain in peace of mind without worry about it.
  3. As developers we will spend less time to maintain separated file with consolidated code and therefore save more money for our company.


Finale

Do you still feel like being intrigued by question why we execute “func” in the same context in which substitution is executed (line 31) by using “apply” method of “Function.prototype” instead of “var result = func (arguments);”, which is going to produce the same result?

Well, it depends...

The result will be the same, but in this particular situation.

As to other situations, the answer to this question can be found in first paragraph on page 173 of “Pro JavaScript Design Patterns” book by Ross Harmes and Dustin Diaz.

Happy programming,

Alex Movshovich,
Software Developer.


interception.js

// functions to be intercepted
var FuncToIntercept_1 = function() {
    return 'FuncToIntercept_1  executed';
};

var FuncToIntercept_2 = function() {
    return 'FuncToIntercept_2  executed';
};

//Callers of functions to be intercepted
var CallerFunc_1 = function() {
    return 'executing CallerFunc_1' + ' => ' + FuncToIntercept_1();
};

var CallerFunc_2 = function () {
    return 'executing CallerFunc_2' + ' => ' + FuncToIntercept_2();
};



//Set initialization function
var SetInitFunc = function () {
    console.log('Set Initialized');
};

//function' interceptor
var FuncInterceptor = function (func) { 
    return function () { // anonimous function as a substitution for 'func'
        // execute 'func' in the same context in which substitution is executed
        var result = func.apply(this, arguments); 
        SetInitFunc(); // execute our code;
        return result; // return result of 'func' execution;
    }
};

//Interception Test
var InterceptionTest = function () {

    // Imitates code execution on servers where our 'plug in' file is missing 
    console.log('!!! Execute calls to target functions before their interception !!!');
    console.log('----------------------------------------');
    console.log(CallerFunc_1());
    console.log(CallerFunc_2());

    console.log('----------------------------------------');
    console.log("!!! Discovering AND Intercepting targets !!!");

    // ======= imitates limits  of our 'plug in' file deployed on selected servers ================

    // ........ here code for SET of new complex capabilities to be initialized...........

    if (FuncToIntercept_1) { //- Discover presence of first target functions in outer scope
        FuncToIntercept_1 = FuncInterceptor(FuncToIntercept_1); //-Intercept by assigning substitution 
    }                                                           //in place of first target
    if (FuncToIntercept_2) { //- Discover presence of second target functions in outer scope
        FuncToIntercept_2 = FuncInterceptor(FuncToIntercept_2); //-Intercept by assigning substitution 
    }                                                           //in place of second target

    // ======== imitates limits  of our 'plug in' file deployed on selected servers ================

    // Imitates code execution on servers where our 'plug in' file is deployed and loaded on document ready 
    console.log('----------------------------------------');
    console.log('!!! Excute calls to target functions after their interception !!!');
    console.log('----------------------------------------');
    console.log(CallerFunc_1()); // execute caller 1
    console.log(CallerFunc_2()); // execute caller 2
};

interception.htm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>
    The journey into JavaScript functionâs interception
    
    </title>
    
    <script type="text/javascript" src="interception.js"></script>

</head>
<body>
    <h1>The journey into JavaScript functionâ interception</h1>
    <h3><a href="http://softomiser.hubpages.com/" >by Alex Movshovich</a></h3>
    <h3>Run <em>InterceptionTest();</em> in Firefox with opened Firebug console </h3>
</body>
</html>

© 2012 softomiser

More by this Author


Comments

No comments yet.

    Sign in or sign up and post using a HubPages Network account.

    0 of 8192 characters used
    Post Comment

    No HTML is allowed in comments, but URLs will be hyperlinked. Comments are not for promoting your articles or other sites.


    Click to Rate This Article
    working