furt furt
furt furt

Reputation: 291

How to use object methods as callbacks to modify object properties in javascript

I am building an interface to a REST API, and in the process of using the javascript request module, I found trouble trying to return values from the callback. Request does not work that way, and you must handle the data from within the callback.

But I need to process and compare data from many requests, so I decided to push the data to some database object from within my callback.

I made an prototype function to be called as the callback to keep the data structure modular.

I am baffled because when I try to modify this.value from within my callback function, the result does not go to the proper place.

I expect the callback function to modify my instance of the database, and to be able to access that change later on after waiting for the callback to finish.
In the code sample below, I show that I am I am able to do exactly this with globalString, but in globalDatabase, the assignment does not survive past the end of the callback function.

I suspect I may be using my object pointers incorrectly. can anyone point out the flaw in how I modify this.value, or provide a good alternative to the way I am using OOP here.

A good solution should be able to assign a value from inside the callback and then access that value from another function which is not called by the callback.

What is the best way to store the data from my callback?

    var Database = function(){
        console.log("<Executing constructor>");
        this.value = "initial constructor data";
    };
    Database.prototype.myCallback = function(error, response, body){
        console.log("<Executing callback>");
        this.value = body;
        globalString = body;
    };
    globalString = "blank";
    globalDatabase = new Database();
    console.log(globalString, "|" ,globalDatabase.value);
    main();
    function main(){
        var request = require('request');
        requestParams = {
            url: "http://ip.jsontest.com/",
            method: "GET",
            json: true
        };
        request(requestParams, globalDatabase.myCallback);
        console.log(globalString, "|" ,globalDatabase.value);
        setTimeout(function() {
            console.log(globalString, "|" ,globalDatabase.value);
        }, 2 * 1000);//seconds wait time for callback to finish
    };

I was able to replicate this problem using callbacks in setTimeout.

var Database = function(){
    console.log("<Executing constructor>");
    this.value = "initial constructor data";
};
Database.prototype.myCallback = function(){
    console.log("<Executing callback>");
    this.value = "callback modified data";
};
d = new Database();//global target for async modification
main();
function main(){
    console.log("First, the object contains: ",d.value);
    setTimeout(d.myCallback, 1 * 1000);//seconds wait time
    console.log("Back in main, the object contains: ", d.value);
    setTimeout(function() {
        console.log("After waiting patiently, the object contains: ",d.value);
    }, 2 * 1000);//seconds wait time
};

Upvotes: 0

Views: 2574

Answers (1)

Kryten
Kryten

Reputation: 15770

This is a well-known "quirk" of Javascript: when you call your myCallback function from the request function (or from setTimeout), the context of the call is the request function - this means that this refers to request, not to your Database object. So, for example, if you call myCallback from a DOM event handler, then this will refer to the DOM element.

There are a number of good answers explaining this: here or here.

Now, for a solution to your specific problem, here's a code sample. I took the liberty of rewriting your second example using ES6 classes, since I think it's a little clearer that way:

class Database {
  constructor() {
    console.log('<Executing constructor>');
    this.value = 'initial constructor data';

    // bind `this` to the `myCallback` function so that whenever we call 
    // `myCallback`, it will always have the correct `this`
    // 
    this.myCallback = this.myCallback.bind(this);
  }

  myCallback() {
    console.log('<Executing callback>');
    this.value = 'callback modified data';
  }
}


let d = new Database(); //global target for async modification

main();
function main(){
  console.log("First, the object contains: ",d.value);
  setTimeout(d.myCallback, 1 * 1000);//seconds wait time
  console.log("Back in main, the object contains: ", d.value);
  setTimeout(function() {
    console.log("After waiting patiently, the object contains: ",d.value);
  }, 2 * 1000);//seconds wait time
};

Notice the call the bind in the constructor. That "replaces" the myCallback method with a new version of the same method where the context is always the this that refers to the class.

Upvotes: 3

Related Questions