0

I have a job class, defined in Javascript as such:

var Job = function() {};
module.exports = Job;

I then define a member function using its prototype. That works fine, until I try to use a value from a database to set the member variable:

// Create from an existing job ID
Job.prototype.createFromID = function( inID, callback ) {

    // Start by making sure we're invalid 
    this.id = "";

    // Connect to database
    var db = MongoClient.connect('mongodb://localhost:27017/nodepurple', function( err, db ) {
        if (err) { return false; }

        // Find the job document we're interested in
        db.collection('jobs').find({ jobID: inID }).limit( 1 ).toArray( function( err, theJobs ) {
            if (theJobs.length == 1) {
                this.id = theJobs[0].jobID;

                // Close the database
                db.close();

                callback( (this.id != ""), this );
            }
        }); // Find
    }); // Connect
}

The purpose of this function is to:

  • Get a MongoDB document defining the specific job I'm interested in (defined by the "inID" parameter.
  • Fill a bunch of member variables of the job instance for which this function was called.

This doesn't work. I think I understand why it doesn't work, when these MongoDB callbacks return, I'm assuming I'm no longer in the right context to make this work, but I'm struggling to see how this can be resolved.

So, how can I take the value MongoDB gives me back for jobID and use that to populate the "id" member variable in the particular Job instance I'm working on?

David van Driessche
  • 6,602
  • 2
  • 28
  • 41

1 Answers1

1

Yes, you're right, inside the callback this doesn't refer to the Job object anymore. To fix that you have a couple of options.

1) Keep a reference to the Job object to use inside the callback to set the id.

Job.prototype.createFromID = function( inID, callback ) {

    this.id = "";
    var self = this;

    var db = MongoClient.connect('mongodb://localhost:27017/nodepurple', function( err, db ) {
        if (err) { 
            return false; 
        }
        db.collection('jobs').find({ jobID: inID }).limit( 1 ).toArray( function( err, theJobs ) {
            if (theJobs.length == 1) {
                self.id = theJobs[0].jobID;
                db.close();
                callback( (self.id != ""), self );
            }
        }); 
    }); 
}

2) Bind the Job object to the callback function

Job.prototype.createFromID = function( inID, callback ) {

    this.id = "";
    var mongoCallback2 = function(err, theJobs) {
        if (theJobs.length == 1) {
            this.id = theJobs[0].jobID;
            db.close();
            callback((this.id != ""), this);
        }
    }
    var mongoCallback1 = function(err, db) {
        if (err) { return false; }
        db.collection('jobs').find({jobID: inID}).limit(1).toArray(mongoCallback2.bind(this)); 
    }); 

    var db = MongoClient.connect('mongodb://localhost:27017/nodepurple', mongoCallback1.bind(this));
}

3) Using arrow functions. Arrow functions do not change the context.

Job.prototype.createFromID = function( inID, callback ) {

    this.id = "";

    var db = MongoClient.connect('mongodb://localhost:27017/nodepurple', (err, db) => {
        if (err) { 
            return false; 
        }
        db.collection('jobs').find({jobID: inID}).limit( 1 ).toArray((err, theJobs) => {
            if (theJobs.length == 1) {
                self.id = theJobs[0].jobID;
                db.close();
                callback((this.id != ""), this);
            }
        }); 
    }); 
}

Arrow functions are not supported by all browser or all browser versions but there are tools out there that can convert you code to work on a wider range of browsers and browsers versions.

Titus
  • 22,031
  • 1
  • 23
  • 33
  • Thanks much! I already thought of method 1, just tested it and of course that works. It also looks awful and grated against any sense of OOP I have built up over the years. But perhaps that's to be expected with Javascript; I'm starting to think that there might be more downsides than upsides to trying to use classes in Javascript. Your second method is interesting, but I'm never going to write code like that; it feels completely upside down. – David van Driessche Mar 23 '17 at 07:46
  • @DavidvanDriessche I added a new option to my answer. I had the same problem with JavaScript OOP but the new syntax (arrow functions and the `class` and `super` keywords) made things easier. – Titus Mar 23 '17 at 12:38
  • Ah, very cool. I'm using this in a node-red project with node.js so I'm assuming I won't have too many problems getting this to work. I'm using your first method now and that works fine, but I'm going to try this second method and read up on this class and super stuff (and the arrows); that feels more like it. Thanks Titus! – David van Driessche Mar 23 '17 at 19:57
  • @DavidvanDriessche Yes, you can use the new syntax in node applications without problems. I'm glad I could help. Good luck. – Titus Mar 23 '17 at 20:03
  • Hi Titus, this works beautifully. I read up a bunch on more advanced Javascript and your method 3 works perfectly. The syntax is still a little unwieldy, but not having to use this fake "self" variable is great and I find this syntax actually easier on the eyes than the regular function call back. Thanks once more! – David van Driessche Mar 24 '17 at 08:15