4

This might be a stupid question, but I can't seem to leverage google because of all the "Closure 101" links...

In summary, given duplicate code that relies on the closure context, is there a way to de-dup the code into a function call while still having the new function rely only on the closure rather than passing everything it needs via parameters?

A rough code example might look like:

function doWork(){
    // initialize variables
    // manipulate variables
    // ...

    $.ajax({
       //...
       success: function(data){
           // DUPLICATE CODE INSTANCE 1 HERE
           // RELIES ON VARIABLES IN THE CLOSURE          
       }
    });

    // More code

    $.ajax({
       //...
       success: function(data){
           // DUPLICATE CODE INSTANCE 2 HERE
           // RELIES ON VARIABLES IN THE CLOSURE          
       }
    });

}

As far as I know, if I de-dup the logic in the success blocks into

function onSuccess(...){
   // ...
}

Then onSuccess is no longer part of the closure, so would need all the closure variables passed as parameters where the current logic is using the closure to access.

Am I wrong about how closures work? Is there a way to "pass the closure" to the onSuccess function rather than passing individual variables?

Mark
  • 4,446
  • 5
  • 26
  • 34

3 Answers3

4

You are not wrong about the closure behavior. What you can do is declare the onSuccess function inside of doWork.

function doWork(...) {
  function onSuccess(...) {
    // ...
  }

  $.ajax({
    //...
    success: onSuccess
  });

  $.ajax({
    //...
    success: onSuccess
  });
}
Annabelle
  • 10,596
  • 5
  • 27
  • 26
  • 1
    Just need to keep in mind that onSuccess will have a different value for this than when you define it. you can set the context in the ajax options, or $.proxy into it, or pass it a bound function.. lots of ways around that. – danp Apr 30 '13 at 00:36
2

Unless you define it inside the closure

function doWork(){
    // initialize variables
    // manipulate variables
    // ...
    function onSuccess(data){
       // DUPLICATE CODE INSTANCE 2 HERE
       // RELIES ON VARIABLES IN THE CLOSURE 
     }


    $.ajax({
       //...
       success: onSuccess
    });

    // More code

    $.ajax({
       //...
       success: onSuccess
    });

}
Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
  • Part of the desire is that the doWork function is getting huge to begin with... I wanted onSuccess to be outside to clean up the code base – Mark Apr 29 '13 at 23:37
  • Also, are variables in doWork that are initialized after the definition of onSuccess still accessible to onSuccess' closure? – Mark Apr 29 '13 at 23:39
  • Yes, all variables inside `doWork` are accessible by `onSuccess`. – Gabriele Petrioli Apr 29 '13 at 23:43
0

What you could do is turn the things you need into the JavaScript equivalent of public variables by using this.var. Then you could pass a reference of this to an object outside the scope of your closure and access the properties and methods of the closure that you passed (from this). Take the following code for example:

Edited from @danp's guidance:

var Work = function Work() {   
    this.closureVar = "hello";

    this.closureOnSuccess = function () {
        console.log("Inner call:" + this.closureVar);
    }

    this.someCall = function() {
        globalOnSuccess(this); //Give the global function a reference to this closure
        this.closureOnSuccess();
    }

    this.someCall();
}

var globalOnSuccess = function (context) { //context is a reference to a closure
    console.log("Outer call:" + context.closureVar); 
}; //Globally scoped function

var work = new Work();

jsFiddle

And another example:

var Work = function Work() {};

Work.prototype = {
    closureVar: "hello",
    closureOnSuccess: function () {
        console.log("Inner call:" + this.closureVar);
    },
    someCall: function () {
        globalOnSuccess(this);
        this.closureOnSuccess();
    }
};

var globalOnSuccess = function (context) {
    console.log("Outer call:" + context.closureVar);
};

var work = new Work();

work.someCall();

jsFiddle

SomeShinyObject
  • 7,581
  • 6
  • 39
  • 59
  • 1
    When you call a function like that, this is the global object (window in a browser) - that's not a good thing. You probably want to make a constructor, and add those members to its prototype, instead of slamming it all into global. – danp Apr 30 '13 at 00:21
  • as in, after you run doWork(), your global object will have closureVar, someCall etc defined _outside_ your function. – danp Apr 30 '13 at 00:23
  • It's not hard to actually stop coding like that, you know ;) You're almost there, just need to read up on this stuff: http://pivotallabs.com/javascript-constructors-prototypes-and-the-new-keyword/ for example. try it, you'll surprise yourself. – danp Apr 30 '13 at 00:30
  • 1
    @danp, I think I understand. Good link. Is the new code any better? – SomeShinyObject Apr 30 '13 at 00:51
  • you need to really lock down what you mean by inner and outer variables, but it's the right path. – danp Apr 30 '13 at 01:26