9

I've written a node script that gets some data by requesting REST API data (using the library request). It consists of a couple of functions like so:

var data = { /* object to store all data */ },
function getKloutData() {
  request(url, function() { /* store data */}
}
// and a function for twitter data

Because I want to do some stuff after fetching all the I used the library async to run all the fetch functions like so:

async.parallel([ getTwitterData, getKloutData ], function() {
    console.log('done');
});

This all works fine, however I wanted to put everything inside a object pattern so I could fetch multiple accounts at the same time:

function Fetcher(name) { 
    this.userID = ''
    this.user = { /* data */ }
    this.init();
}
Fetcher.prototype.init = function() {
    async.parallel([ this.getTwitterData, this.getKloutData ], function() {
        console.log('done');
    });
}
Fetcher.prototype.getKloutData = function(callback) {
    request(url, function () { /* store data */ });
};

This doesn't work because async and request change the this context. The only way I could get around it is by binding everything I pass through async and request:

Fetcher.prototype.init = function() {
    async.parallel([ this.getTwitterData.bind(this), this.getKloutData.bind(this) ], function() {
        console.log('done');
    });
}
Fetcher.prototype.getKloutData = function(callback) {
    function saveData() {
        /* store data */
    }


    request(url, saveData.bind(this);
};

Am I doing something basic wrong or something? I think reverting to the script and forking it to child_processes creates to much overhead.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
askmike
  • 1,900
  • 4
  • 21
  • 27

3 Answers3

9

You're doing it exactly right.

The alternative is to keep a reference to the object always in context instead of using bind, but that requires some gymnastics:

Fetcher.prototype.init = function() {
    var self = this;
    async.parallel([
        function(){ return self.getTwitterData() },
        function(){ return self.getKloutData() }
    ], function() {
        console.log('done');
    });
}

Fetcher.prototype.getKloutData = function(callback) {
    var self = this;

    function saveData() {
        // store data
        self.blah();
    }

    request(url, saveData);
};

You can also do the binding beforehand:

Fetcher.prototype.bindAll = function(){
    this.getKloutData = this.prototype.getKloutData.bind(this);
    this.getTwitterData = this.prototype.getTwitterData.bind(this);
};

Fetcher.prototype.init = function(){
    this.bindAll();
    async.parallel([ this.getTwitterData, this.getKloutData ], function() {
        console.log('done');
    });
};
Ricardo Tomasi
  • 34,573
  • 2
  • 55
  • 66
  • 3
    The Underscore library has a handy function called `bindAll` that makes this less painful. And if you opt for CoffeeScript, you can just define the methods with the fat arrow and you won't have to do any explicit binding at all. – Jimmy Jul 11 '12 at 09:05
3

You can save this into another variable:

var me = this;

Then me is your this.

Charles
  • 11,367
  • 10
  • 77
  • 114
0

Instantiate object with this function:

function newClass(klass) {
    var obj = new klass;

    $.map(obj, function(value, key) {
        if (typeof  value == "function") {
            obj[key] = value.bind(obj);
        }
    });

    return obj;
}

This will do automatic binding of all function, so you will get object in habitual OOP style, when methods inside objects has context of its object.

So you instantiate you objects not through the:

var obj = new Fetcher();

But:

var obj = newClass(Fetcher);
  • my original question was in a nodejs environment where jQuery does not make sense. Also the `bindAll` methods in underscore and lodash (libs working in all JS environments) are designed to do this for you. – askmike Dec 07 '14 at 14:20