Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 749
MahatmaManic Avatar asked Dec 05 '10 07:12

MahatmaManic


2 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);
}
like image 161
Ivo Wetzel Avatar answered Oct 11 '22 23:10

Ivo Wetzel


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.

like image 45
Justin Cormack Avatar answered Oct 11 '22 23:10

Justin Cormack