How do I emit to an eventListener from inside a nested function in Node.js (javascript scoping issue)

StackOverflow https://stackoverflow.com//questions/9631989

  •  10-12-2019
  •  | 
  •  

Question

I am writing code below that parses a sites API one at a time, than tells an event queue it is ready for the next object to parse. I am having issues since I am still new to javascript scoping, and would like to emit from SiteParser or call the emitForNext function. I cannot seem to bring emitForNext into scope in the error callback.

   function SiteParser(){
        this.emitForNext = function  (message) {
            this.emit("next", message);
        };

        this.pullJSON = function (path, processJSON) { //processJSON is a callback function    
            var options = {
                host: 'www.site.com',
                port: 80,
                path: path
            }

            //console.log("... processing "+path); 

            //pulls the entire json request via chunks
            http.get(options, function  (res) {
                var resJSON = ''; //stores the comment JSON stream given in the res
                res.on('data',  function (chunk) {
                    resJSON+=chunk;   
                });  
                res.on('end', function () {
                    var obJSON = (JSON.parse(resJSON));  

                    if (obJSON.hasOwnProperty("error")){ 
                        console.log(obJSON);
                        console.log('... ', path, ' does not exist');
                        //
                        //NEED A NEXT EVENT EMMITER HERE NEED TO FIGURE OUT SCOPE
                        //
                        //   
                    } else {
                        processJSON(obJSON); //call the callback function
                    }
                }) ;
            }).on('error', function  (e) {
                emitForNext("got error: " + e.message);
            });
        };
    }
Was it helpful?

Solution

JavaScript has function scoping, if you declare a variable with the var keyword, it will be local to the current function. When you access a variable, it will look to the scope chain which consist of the current function, it's parent function, …. Try:

function one() {
    var foo = 'foo';

    function two() {
        console.log(foo) // undefined. I'll explain this
        var foo = 'bar';
        console.log(foo) // bar
    }

    two()
    console.log(foo) // foo
}
one()

Most of the time we define variables at the beginning of functions, because a variable defined in function's body get hoisted. Basically, it means that it's available in the whole function, even before it's defined but in this case it's value is undefined.

For example if a variable is not defined we normally get a ReferenceError, but in the snippet below, both console.log() just print undefined.

function foo() {
     console.log(bar);
     if (0) {
         var bar = 'bar';
     }
     console.log(bar);
}

So, a common practice is that, when you write long functions, you map this to self.

function SiteParser() {
    var self = this;
    // ...
    .error('error', function(err) {
        self.emitForNext("got " + err.message);
    })
}

You should not write all your methods in the constructor, it's only usefull sometimes when we want privacy, but in this case you'd better use prototypes.

Putting this together, I would write:

var SiteParser = function() {};

SiteParser.prototype.emitForNext = function(message) {
    this.emit("next", message);
};

SiteParser.prototype.pullJSON = function(path, processJSON) { 
    var self    = this,
        options = {
            host: 'www.site.com',
            port: 80,
            path: path
        };

    http.get(options, function(res) {
        // ...
    }).on('error', function  (e) {
        self.emitForNext("got error: " + e.message);
    });
};

OTHER TIPS

To be able to access emitForNext, you need to call self.emitForNext, where self points to your instance of SiteParser.

Like so:

function SiteParser(){

    this.emitForNext = function  (message) {

        this.emit("next", message);

    };



    this.pullJSON = function (path, processJSON) { //processJSON is a callback function    

        var options = {

            host: 'www.site.com',

            port: 80,

            path: path

        };



        var self = this;



        //console.log("... processing "+path); 



        //pulls the entire json request via chunks

        http.get(options, function  (res) {

            var resJSON = ''; //stores the comment JSON stream given in the res

            res.on('data',  function (chunk) {

                resJSON+=chunk;   

            });  

            res.on('end', function () {

                var obJSON = (JSON.parse(resJSON));  



                if (obJSON.hasOwnProperty("error")){ 

                    console.log(obJSON);

                    console.log('... ', path, ' does not exist');





                    self.emitForNext(path + ' does not exist');

                } else {

                    self.emitForNext('Successfully parsed the response');

                    processJSON(obJSON); //call the callback function

                }

            }) ;

        }).on('error', function  (e) {

            self.emitForNext("got error: " + e.message);

        });

    };

}

However, it looks like you'd rather manage what you'll do next (like parsing the next object) in you callback, ie. in the body of processJSON.

You need to store link to 'this'' object in SiteParser local scope.

 function SiteParser () {
    var that = this; // store link to 'this' in local scope

    this.emitForNext = function (message) {
        this.emit("next", message);
    };

    this.pullJSON = function (path, processJSON) { //processJSON is a callback function    
        var options = {
            host: 'www.site.com',
            port: 80,
            path: path
        }

        //console.log("... processing "+path); 

        //pulls the entire json request via chunks
        http.get(options, function  (res) {
            var resJSON = ''; //stores the comment JSON stream given in the res
            res.on('data',  function (chunk) {
                resJSON+=chunk;   
            });  
            res.on('end', function () {
                var obJSON = (JSON.parse(resJSON));  

                if (obJSON.hasOwnProperty("error")){ 
                    console.log(obJSON);
                    console.log('... ', path, ' does not exist');
                    that.emitForNext();
                } else {
                    processJSON(obJSON); //call the callback function
                }
            }) ;
        }).on('error', function (e) {
            that.emitForNext("got error: " + e.message);
        });
    };
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top