Click here to Skip to main content
15,890,717 members
Articles / Programming Languages / Javascript
Tip/Trick

Decouple JavaScript Function Call to Multiple Callbacks without Changing Caller or Callee

Rate me:
Please Sign up or sign in to vote.
3.67/5 (3 votes)
22 Oct 2014CPOL1 min read 12.9K   1   2
A bit of street magic with JavaScript closures and jquery callbacks

Introduction

Let's imagine that you have some legacy or third party code, that has well-known, but awful API. In my case, it was js object that called my functions directly by function name, without support for callbacks.
So if I needed receive some notification from this object, I was required to directly assign appropriate function, e.g.:

JavaScript
// 'api' - is that nasty object
api.OnSomeEvent = function (argument){
// my logic
}

Sometimes, I needed to receive these callbacks in multiple places, so I was expected to waste my time creating proxy objects that receive notifications from API and transfer them to end destination. It may look like:

JavaScript
var proxy = {};
proxy.Callbacks = $.Callbacks();
proxy.OnSomeEventHandler = function(argument){
  proxy.Callbacks.fire(argument);
}

api.OnSomeEvent = proxy.OnSomeEventHandler;

// ...
// ... here is some code that adds callbacks to proxy.Callbacks ...
// ...

FYI, this is not real code, I know that my proxy object is miserable, and code is ugly, but this is the simplified version for quick understanding.

What is worse, sometimes API does not provide events that I needed, and I had no chance to change API. So I need to 'hook' required functions (thank goodness they were accessible), e.g.:

JavaScript
var proxy = {};
proxy.Callbacks = $.Callbacks();
proxy.OnStupidFunctionHook = function(argument){
  proxy.Callbacks.fire(argument);
  proxy.HookedStupidFunction();
}

proxy.HookedStupidFunction = api.StupidFunction;
api.StupidFunction = proxy.OnStupidFunctionHook;

// ...
// ... here is some code that adds callbacks to proxy.Callbacks ...
// ...

Using the Code

Certainly, I was unhappy to write sheets of code for such a trivial task. But JavaScript has an ace in the hole - closures. With such a great instrument, I managed to solve each of the previously described problems with one line of code:

JavaScript
JsUtils.SetCallbackHook(api.StupidFunction, function(arguments){ /* I am the callback ! */ });

JsUtils.SetCallbackHook(api.OnSomeCallback, function(arguments){ /* I am the callback ! */ });

JsUtils.SetCallbackHook(api.OnSomeCallback, function(arguments){ /* I am the another one callback ! */ });

But let's take a look at what lays under the bonnet of JsUtils.SetCallbackHook:

JavaScript
var JsUtils = new function() {
    var self = this;
   
    self.SetCallbackHook = function (sourceFunction, targetCallback) {
        var callBackClosure = function () {
            sourceFunction.callbacks.fireWith(this, $.makeArray(arguments));
        };
        // 1. if function was undefined before - just passing closure function
        if (!sourceFunction) {
            sourceFunction = callBackClosure;
        }        
        // 2. if function was defined before, and it is note our closure - hook it, and assign closure
        if(sourceFunction.toString() != callBackClosure.toString()){
            sourceFunction = self.SetCallbackHook(callBackClosure, sourceFunction);
        }
        // 3. make sure our closure has callback
        if (!sourceFunction.callbacks) {
            sourceFunction.callbacks = $.Callbacks();
        }
        // 4. make sure our closure has callback
        sourceFunction.callbacks.add(targetCallback);
        return sourceFunction;
    };
};

As you understand from the comments, we replace native function with our closure, that stores also list of callbacks. And each time we assign new callback it is added to the list, and raised once closure is called.

I did not wrap JsUtils to namespace to avoid additional confusion that beginners may feel. You may do it in your own project if need be.

You may play with the working example in this fiddle.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) MYOB
New Zealand New Zealand
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 2 Pin
majid torfi23-Oct-14 4:11
professionalmajid torfi23-Oct-14 4:11 
GeneralRe: My vote of 2 Pin
Ivan Perevernykhata23-Oct-14 4:15
Ivan Perevernykhata23-Oct-14 4:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.