How can I maintain scope in node.js callbacks?

I'm an experienced software developer, but pretty new to JS and to node. I'm not a big fan of super-nested code, so I've been trying to break callbacks out into their own functions. I'm having trouble though with figuring out how to maintain scope when the callback fires. Digging around I read that if I created a closure over the callback it would work, but it doesn't seem to work the way I expected it would.

Here's a very simple version of the kind of code that isn't working for me:

function writeBody()
{
    res.end("<h1> Hooray! </h1>");
}

http.createServer(function(req, res)
{
    res.writeHead('Content-Type', 'text/html');
    setTimeout(function(){writeBody()}, 2000);
}).listen(8000);

I thought that by wrapping the writeBody() call in the function() closure I would have the scope I needed after the timeout, but when writeBody() fires I get

ReferenceError: res is not defined

Can anyone tell me what boneheaded thing I am doing wrong?

Answers


Basically that not how closures work, functions inherit their outer scopes that's how it works.

// this function only inherits the global scope
function writeBody()
{
    res.end("<h1> Hooray! </h1>");
}

http.createServer(function(req, res) // a new local varaible res is created here for each callback
{
    res.writeHead('Content-Type', 'text/html');
    // annonymous function inheris both the global scope
    // as well as the scope of the server callback
    setTimeout(function(){

        // the local variable res is available here too
        writeBody()

    }, 2000);
}).listen(8000);

To make it work just pass the res object into the function, as it's available in the timeout callback.

function writeBody(res)
{
    // NOT the same variable res, but it holds the same value
    res.end("<h1> Hooray! </h1>");
}

http.createServer(function(req, res)
{
    res.writeHead('Content-Type', 'text/html');
    setTimeout(function(){
        writeBody(res); // just pass res
    }, 2000);
}).listen(8000);

But you need to watch out for things like this:

for(var i = 0; i < 10; i++) { // only one i gets created here!()
    setTimeout(function() {
        console.log(i); // this always references the same variable i
    }, 1000);
}

This will print 10 ten times, because the reference is the same and i gets incremented all the way up to 10. If you want to have the different numbers you need to create a new variable for each one, either by wrapping the setTimeout into an anonymous self function which you pass in the i as a parameter, or by calling some other method which sets up the timouet and receives the i as a parameter.

// anoynmous function version
for(var i = 0; i < 10; i++) {
    (function(e){ // creates a new variable e for each call
        setTimeout(function() {
            console.log(e); 
        }, 1000);
    })(i); // pass in the value of i
}

// function call version
for(var i = 0; i < 10; i++) {
    createTimeoutFunction(i);
}

You can also make the functions nested, so they share scope, ie

http.createServer(function(req, res)
{

    function writeBody()
    {
        res.end("<h1> Hooray! </h1>");
    }

    res.writeHead('Content-Type', 'text/html');
    setTimeout(function(){writeBody()}, 2000);
}).listen(8000);

I often find this is easier than always passing a bunch of variables in to keep in scope, although it means you cannot reuse the function elsewhere.


You can pass response in your callback so:

http.createServer(function(req, res)
{
    res.writeHead('Content-Type', 'text/html');
    setTimeout(function(){writeBody(res)}, 2000);
}).listen(8000);

I actually like Justin Cormack's answer. Here is a more extreme example of some recent coding of mine.

var Func4 = function(req, res)
{
  var collectionName = "parts";
  var f0 = function() {mongodbClient.collection(collectionName, f1);};
  var f1 = function(err, coll) {coll.ensureIndex("item", f2);};
  var f2 = function(err, indexname)
  {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write("index name = " + indexname);
    res.end();
  };
  f0();
};

Most people would tell me (and they do) that this is the correct way to write that code.

var Func4 = function(req, res)
{
  var collectionName = "parts";
  mongodbClient.collection(collectionName, function(err, coll) {
    coll.ensureIndex("item", function(err, indexname) {
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.write("index name = " + indexname);
      res.end();
    })});
};

Perhaps I am a n00b, but I find nested callbacks a bit hard to follow. I also admit a bunch of f0,f1,f2 functions is lame. Either way, this is a good example of scope.


Need Your Help

How to print NSUserDefaults content in Swift

ios swift nsuserdefaults

How do you print all the content of NSUserDefaults?