1

I am well experienced developer but new to java script and nodejs , I apologize if this question has been answered 'as-is' but even though I have went over multiple examples and stackoverflow answers , I did not find a simple complete example of prototypical class with correct 'self' var scoping and bind(this). I tried both and both went wrong ... I will appreciate your help. I tried putting var self = this; In beginning of my functions declarations , but when running , it does not actually go through the function code when it is set to the prototype , and so , 'this' is not set correctly.

   /**
     * Module Dependencies
     */
    var cheerio = require('cheerio');
    var http = require('http');

    /**
     * Export
     */

    module.exports = SimplePageGetter;

    function SimplePageGetter(pageLink) {
        this._pageLink = pageLink;
    }

    SimplePageGetter.prototype.getPage = function () {
        var self = this;
        http.request(self._pageLink, self._resultsPageHttpGetCallback).end();
    };

    SimplePageGetter.prototype._resultsPageHttpGetCallback = function (response) {
        var pageBody = '';
        var self = this;
        //another chunk of data has been recieved, so append it to `str`
        response.on('data', function (chunk) {
            pageBody += chunk;
        });

        //the whole response has been recieved, so we just print it out here
        response.on('end', function () {
            self._parsePage(pageBody);
        });
    };

SimplePageGetter.prototype._parsePage = function (body) {
  console.log('page parsed');
}

For some reason , 'self' is correct when calling getPage but will be http module ClientRequest and not the object on _resultsPageHttpGetCallBack . What am I please doing wrong ?

Thank you ,

James

James Roeiter
  • 792
  • 1
  • 8
  • 22

1 Answers1

4

Setting self in the calling function doesn't do anything to change what this will be in the function being called. So looking at this:

SimplePageGetter.prototype.getPage = function () {
        var self = this;
        http.request(self._pageLink, self._resultsPageHttpGetCallback).end();
    };

That's still just passing a reference to the self._resultsPageHttpGetCallback function to http.request. http.request will still call it as just a normal function, not a method, and so this in _resultsPageHttpGetCallback will be either undefined (strict mode) or the global object (loose mode).

The self pattern use useful for functions created in the same scope (or a nested scope), for instance:

function someMethod() {
    var self = this;
    http.request(self._pageLink, function(err, data) {
        // Use `self` here to access object info
    }).end();
}

That works because the anonymous function I'm passing into http.request closes over (has a reference to) the context where it's created, and that context has the self variable, and so the function can access the self variable.

For what you're doing, Function#bind would be more appropriate:

SimplePageGetter.prototype.getPage = function () {
        http.request(this._pageLink, this._resultsPageHttpGetCallback.bind(this)).end();
    };

Function#bind creates a new function that, when called, will call the original function with this set to a specific value.

More about this:


Just for reference, here's the Function#bind pattern applied to your complete code example:

/**
 * Module Dependencies
 */
var cheerio = require('cheerio');
var http = require('http');

/**
 * Export
 */

module.exports = SimplePageGetter;

function SimplePageGetter(pageLink) {
    this._pageLink = pageLink;
}

SimplePageGetter.prototype.getPage = function () {
    http.request(this._pageLink, this._resultsPageHttpGetCallback.bind(this)).end();
};

SimplePageGetter.prototype._resultsPageHttpGetCallback = function (response) {
    var pageBody = '';

    response.on('data', function (chunk) {
        pageBody += chunk;
    });

    //the whole response has been recieved, so we just print it out here
    response.on('end', function () {
        this._parsePage(pageBody);
    }.bind(this));
};

SimplePageGetter.prototype._parsePage = function (body) {
    console.log('page parsed');
};

You might look into the new features of ES2015 (aka ES6), many of which you can use in NodeJS now as of v4 because the underlying V8 engine supports them (alternately, you can use a transpiler to produce ES5 code from ES6 input).

Here's the above using ES2015's:

  • ...arrow functions, which inherit this from the context in which they're defined, making self unnecessary.

  • ...the class keyword, which provides a more concise way to write constructors and prototypes.

  • ...the let keyword, just because, you know, it's ES2015 code. :-)

Applying those:

/**
 * Module Dependencies
 */
let cheerio = require('cheerio');
let http = require('http');

class SimplePageGetter {
    constructor(pageLink) {
        this._pageLink = pageLink;
    }

    getPage() {
        http.request(this._pageLink, response => {
            this._resultsPageHttpGetCallback(response);
        }).end();
    }

    _resultsPageHttpGetCallback(response) {
        let pageBody = '';

        response.on('data', chunk => {
            pageBody += chunk;
        });

        //the whole response has been recieved, so we just print it out here
        response.on('end', () => {
            this.parsePage(pageBody);
        });
    }

    _parsePage(body) {
        console.log('page parsed');
    }
}

/**
 * Export
 */

module.exports = SimplePageGetter;

Note that class is not hoisted like function declarations, so the standard place for exporting is usually at the bottom of the module. If you have just the one export (as you seem to in this case), though, you could do

module.exports = class SimplePageGetter {
    //...
};

Last but not least: Unless you really need _resultsPageHttpGetCallback and _parsePage to be properties on the object (which are public), I would probably make them private functions instead, which either accept the SimplePageGetter instance as a standard argument, or expect to be called with this referring to it even though they aren't methods.

Here, they take an argument:

/**
 * Module Dependencies
 */
let cheerio = require('cheerio');
let http = require('http');

class SimplePageGetter {
    constructor(pageLink) {
        this._pageLink = pageLink;
    }

    getPage() {
        http.request(this._pageLink, response => {
            resultsPageHttpGetCallback(this, response);
        }).end();
    }
}

function resultsPageHttpGetCallback(getter, response) {
    let pageBody = '';

    response.on('data', chunk => {
        pageBody += chunk;
    });

    //the whole response has been recieved, so we just print it out here
    response.on('end', () => {
        parsePage(getter, pageBody);
    });
}

function parsePage(getter, body) {
    console.log('page parsed');
}

/**
 * Export
 */

module.exports = SimplePageGetter;

Here, they expect this to be set, so we call them via Function#call:

/**
 * Module Dependencies
 */
let cheerio = require('cheerio');
let http = require('http');

class SimplePageGetter {
    constructor(pageLink) {
        this._pageLink = pageLink;
    }

    getPage() {
        http.request(this._pageLink, response => {
            resultsPageHttpGetCallback.call(this, response);
        }).end();
    }
}

function resultsPageHttpGetCallback(response) {
    let pageBody = '';

    response.on('data', chunk => {
        pageBody += chunk;
    });

    //the whole response has been recieved, so we just print it out here
    response.on('end', () => {
        parsePage.call(this, pageBody);
    });
}

function parsePage(body) {
    console.log('page parsed');
}

/**
 * Export
 */

module.exports = SimplePageGetter;
Community
  • 1
  • 1
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639