Search code examples
javascriptjqueryajaxclosuresprototypal-inheritance

Sensible approach to callbacks on object prototype methods in JavaScript/jQuery?


Is what I've done below a sensible approach to allow callbacks to run on functions defined in an object's prototype, such that the scope is correct?

I've been wrestling with the correct way to set the value of this when an object's prototype method is the one to run in response to a callback which might originate from an AJAX request or from a click binding or whatever.

Here is a simplified annotated version:

// everything is inside an object which provides the namespace for the app

var namespace = {

// a fairly vanilla object creation routing, which uses the prototype
// approach for defining the functions on the object

newObj : function(params) {
    var MyObj = function(params) {
        this.property = params.property
    };
    MyObj.prototype = namespace.ObjPrototype;
    return new MyObj(params);
},

// the prototype itself, which defines 2 related functions

ObjPrototype : {

    // The first is called to do some form of asynchronous operation
    // In this case it is an ajax call

    doAsync: function (params) {
        $.ajax({
            type: "get",
            url: params.url,
            data: params.data,
            dataType: params.datatype,
            success: namespace.objClosure(this, "asyncSuccess", ["data"]),
        });

        // the final line above is the key here - it asks a function (below)
        // for a closure around "this", which will in turn run the 
        // function "asyncSuccess" (defined next) with the argument "data"
    },

    // This is the actual callback that I want to run.  But we can't
    // pass this.asyncSuccess to the ajax function above, because the
    // scope at execution time is all wrong

    asyncSuccess : function(params) {
        this.property = params.data;
    },
},

// This is the bit I sort of invented, to help me around this problem.
// It returns a function which provides a closure around the object
// and when that returned function is run it inspects the requested
// arguments, and maps them to the values in the JS default
// "arguments" variable to build a parameters object which is then
// passed to the function on the object

objClosure : function(obj, fn, args) {
    return function() {
        if (args) {
            var params = {};
            for (var i = 0; i < args.length; i++) {
                params[args[i]] = arguments[i];
            }
            obj[fn](params);
        } else {
            obj[fn]();
        }

    }
}

}

Now, obviously the actual target callback MyObj.asyncSuccess needs to know that it's going to get a params object, and what structure it will be, and that knowledge has to be shared by the invoking function MyObj.doAsync, but otherwise this seems to work well.

My question is - am I totally mad? Have I missed something obvious that would solve this problem for me in a simpler/less convoluted way? Am I just too far down the rabbit hole by this stage?

I've read around a lot of questions on SO and they have all addressed part of my question, but I don't seem to have got to the bottom of a generally accepted solution for this. I can't be the only person who's ever wanted to do this :)

Edit

I've accepted the answer below, but you need to read all the comments too for it to come together. Thanks folks for your help!


Solution

  • aren't you over complicating things? see if the below code will help you. i did not completely understand your intent but the below code should help you

    function newObj(params) {
        function asyncSuccess(params) {
            this.property = params.data;
        }
    
        function objClosure(obj, fn, args) {
            return function() {
                if (args) {
                    var params = {};
                    for (var i = 0; i < args.length; i++) {
                        params[args[i]] = arguments[i];
                    }
                    obj[fn](params);
                } else {
                    obj[fn]();
                }
    
            }
        }
        this.property = params.property
        this.doAsync = function (params) {
        console.log('reached async');
            $.ajax({
                type: "get",
                url: params.url,
                data: params.data,
                dataType: params.datatype,
                success: objClosure(this, "asyncSuccess", ["data"]),
            });       
        }    
    }
    var k = new newObj({'property':'xyz'});
    k.doAsync();
    

    After seeing the comment from "GameAlchemist" i looked into objClosure function i think we can further improvise by using below code: I am still not sure what the value of this.property or data is to give a proper solution hence just assuming few things

    function newObj(params) {
        function asyncSuccess(params) {
            this.property = params ? params.data : null;
        }
    
        function objClosure(args) {
            return function() {
                if (args) {
                    var params = {};
                    for (var i = 0; i < args.length; i++) {
                        params[args[i]] = arguments[i];
                    }
                    asyncSuccess(params);
                } else {
                    asyncSuccess();
                }
    
            }
        }
        this.property = params.property
        this.doAsync = function (params) {
        console.log('reached async');
            $.ajax({
                type: "get",
                url: params.url,
                data: params.data,
                dataType: params.datatype,
                success: objClosure(["data"]),
            });       
        }    
    }
    

    Few issues here: if you are already passing params.data to data i.e. data:params.data how can you again assign the value this.property = params.data? Few things are confusing but i hope the above solution works : )