JM Santos JM Santos - 2 months ago 14
Javascript Question

Javascript: How to access local variable in callback function?

I have a formidable form which parse the request. Then along with this request is the file being uploaded.. In formidable you can listen to an event if there's a file.

var form = new formidable.IncomingForm({
uploadDir: __dirname + '/temp',
keepExtensions: true
});


This is where I will listen to the event

form.on('file', function(name, file){
var file = file;
fs.readFile(file.path, readFileCallback);
});

function readFileCallback(err, contents){
console.log(file);
if (err) throw err;
....
}


My first code was a chain of callback functions and it's kind of hard to read and maintain so I switch with this approach where I would declare functions then call it as a callback instead of like this:

form.on('file', function(name, file){
fs.readFile(file.path, function(err, contents){
console.log(file);
if (err) throw err;
....
});
});


With this kind of approach, I could access the outside variable which is
file
. I'm wondering what is the difference between the two in accessing the outside variables. Thanks in advance.

Answer

It's a matter of scope. Code has access to the variables declared within the function, its containing function (if any), its containing function (if any), and so on, and then globals.

In your first example, readFileCallback is declared outside the form.on callback, and so it doesn't have access to things inside the form.on callback.

In your second example, the function is inside the form.on callback, and so it does have access to the things inside it.

Note that in the second example, in theory a new function is created each time the callback is called. That's fine, JavaScript engines are really fast at creating functions (and good ones will reuse the code even though a separate function object is created).

Normally you want to create the function at the outermost location where it has access to everything it needs. So in your case, that would be inside form.on, but outside the readFile callback. Which is exactly where your second example has it. But you can use a named function like your first example if you like, just put it in form.on's callback:

form.on('file', function(name, file){
   fs.readFile(file.path, readFileCallback);
   function readFileCallback(err, contents){
      console.log(file);
      if (err) throw err;
      .... 
   }
});

Let's take an example where everything had a simple name, and follow through two calls:

function outer(outerArg) {
    function middle(middleArg) {
        function inner(innerArg) {
            console.log("innerArg = " + innerArg);
            console.log("middleArg = " + middleArg);
            console.log("outerArg = " + outerArg);
        }

        inner(middleArg.toLowerCase());
    }

    middle(outerArg.toUpperCase());
}

outer contains middle which contains inner, and outer calls middle (and middle calls inner). A call:

outer("Test1");
  1. outer gets the arg "Test1"
  2. It calls middle with "TEST1"
  3. It calls inner with "test1"
  4. inner outputs:

    innerArg = test1
    middleArg = TEST1
    outerArg = Test1
    

So far, so simple, but it's more exciting than that: What if middle returns a function that calls inner, instead of calling it immediately, and outer returns returns middle's return value?

function outer(outerArg) {
    function middle(middleArg) {
        function inner(innerArg) {
            console.log("innerArg = " + innerArg);
            console.log("middleArg = " + middleArg);
            console.log("outerArg = " + outerArg);
        }
        function caller() {                         // ***
            inner(middleArg.toLowerCase());         // ***
        }                                           // ***
        return caller;                              // ***
    }

    return middle(outerArg.toUpperCase());          // ***
}

Now, calling outer doesn't have any output at all:

var f = outer("Test2");

But then calling the function middle returned (caller) does:

f();

Output:

innerArg = test2
middleArg = TEST2
outerArg = Test2

The arguments still exist after outer and middle return! But it's even more interesting:

var f1 = outer("Test3");
var f2 = outer("Test4");
f2();  // Note -- calling the second one first
f1();

Output:

innerArg = test4
middleArg = TEST4
outerArg = Test4
innerArg = test3
middleArg = TEST3
outerArg = Test3

So that means, two outerArgs still existed after both calls to outer had finished, along with two middleArgs. How?

They exist on objects attached to the functions:

  • Calling outer creates an execution context (an object), which amongst other things (and leaving out a lot of details) holds the arguments and local variables for that call to outer. Let's call it the "outer context." It also has a reference to the execution context containing it (the global context, in our code). Normally that object gets cleaned up when a functon returns...
  • ...but outer creates a function, middle. When you create a function, the current execution context is attached to the function. That's how it has access to the variables and such in that outer context.
  • outer calls middle, creating an inner execution context, and middle creates two other function (inner and caller), which each get that inner context attached to them. middle then returns caller, so caller exists after the call to middle completes. Since caller has a reference to the inner execution context, the context continues to exist (just like any other object), even though middle has returned. Since that context has a reference to inner, inner also continues to exist.
  • outer returns the return value of middle (which is caller), and so that means caller still exists when outer returns, which means the inner context it refers to still exists, which means inner still exists, and the outer context still exists because the inner context has a reference to it.

...which is how f1 and f2 have access to those arguments after outer returns: When you run them, they look up the values in the contexts attached to them.

Comments