10

I know that javascript does not use Class, at least not in common sense`. I will like to know how to return and save an AJAX return value in a class variable rather than calling multiple methods within the callback.

var Reader = function(){
  //Initialize some variables
  this.data = null;
}

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     this.data = data;
   });
}

Reader.prototype.searchData = function(param){
   //Needs access to this.data
}
Reader.prototype.findData = function(id){
  //Needs access to this.data
}

Reader.prototype.deleteItem = function(id){
  // Needs access to this.data
}

In the above code, whatever function that needs access to the data property needs to be called within the ajax success callback, so If I have ten methods that need data, I will have to line all of them up within the callback, which I do not feel is right. How do I minimise the number of functions in the callback and ensure in some other ways that the function is successful and data is saved the instance variable data.

George
  • 3,757
  • 9
  • 51
  • 86
  • there are multiple ways to solve this, it depends on what exactly is the behavior you need – NaN Mar 14 '16 at 11:53
  • You can set data = undefined for error in AJAX call or create some error flag. So whenever data is undefined or you have error flag - you know if your AJAX call was success. – S. Nadezhnyy Mar 14 '16 at 11:56
  • you can use $.extend function to marge all the function and add different property – damon Mar 14 '16 at 12:03
  • 2
    I doubt that `this` in `Ajax.success(function(data){this.data = data;});` works. You best use arrow functions, `self = this;` or `bind(this);` – Redu Mar 22 '16 at 15:30

13 Answers13

7

The essensential part is how to handle asyncronous data with JavaScript. There are some well tested solutions to this: functions and promises.

In both cass the Reader should have a constructor which assigns the data like this:

function Reader(data) {
   this.data = data;
}

The callback approach requires a factory function, which has a callback.

function getReader(url, callBack) {
   Ajax.success(function(data){
      callBack( new Reader(data) );
   });
}

And use it like

getReader(url, function(reader) {
    reader.searchData();
});

The Promise -approach does not require immediately a callback so the result can be stored in a variable and passed around as a variable which has some advantages:

function getReaderPromise(url) {
   return new Promise( function( resolve, reject ) {
     Ajax.success(function(data){
      resolve( new Reader(data) );
     });    
   });
}

However, using the promise usually requires calling the then function of the promise:

getReaderPromise(url).then( function(reader) {
    reader.searchData();
});
// Fat arrow syntax
getReaderPromise(url).then( (reader) => {
    reader.searchData();
});  

In the future you can get rid of the callbacks with Promises using ES6 generators with yield like

let reader = yield getReaderPromise( url );

As explained in here: https://davidwalsh.name/async-generators

Tero Tolonen
  • 4,144
  • 4
  • 27
  • 32
4

You could try something like:

var Reader = function(){
  //Initialize some variables
  this.data = null;
}

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   this.data = Ajax({url: urlPath}); // return promise
}

Reader.prototype.searchData = function(param){
   //Needs access to this.data
   if(this.data){
       this.data.then(...);
   }

}
Reader.prototype.findData = function(id){
  //Needs access to this.data
   if(this.data){
       this.data.then(...);
   }
}

Reader.prototype.deleteItem = function(id){
  // Needs access to this.data
   if(this.data){
       this.data.then(...);
   }
}
Dotun
  • 91
  • 2
3

In your code:

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     this.data = data;
   });
}

"this" is not the Reader instance context, you should change it to

Reader.prototype.makeAjaxCall = function(urlPath){
   var myThis = this;
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     myThis .data = data;
   });
}

To keep the pointer to Reader instance in myThis variable and access inside the success function

VinhNT
  • 1,091
  • 8
  • 13
1

this is what I think is a nice way to solve it, it needs a couple of callbacks though

var Reader = function(){
  //Initialize some variables
  this.data = null;
}

Reader.prototype.makeAjaxCall = function(urlPath, cb) {
   //Make and Ajax call and return some value
   Ajax.success(function(data) {
     this.data = data;
     if(cb) cb();
   });
}

Reader.prototype.searchData = function(param){
    var self = this;
    if(!this.data) {
        // data doesn't exist, make request
        this.makeAjaxCall(urlPath, function() {
            // when is ready, retry
            self.searchData(param);
        });
        return;
    }
   // do whatever you need...
   // data exists
}
Reader.prototype.findData = function(id){
    var self = this;
    if(!this.data) {
        // data doesn't exist, make request
        this.makeAjaxCall(urlPath, function() {
            // when is ready, retry
            self.findData(id);
        });
        return;
    }
   // do whatever you need...
   // data exists
}

Reader.prototype.deleteItem = function(id){
    var self = this;
    if(!this.data) {
        // data doesn't exist, make request
        this.makeAjaxCall(urlPath, function() {
            // when is ready, retry
            self.deleteItem(id);
        });
        return;
    }
   // do whatever you need...
   // data exists
}
NaN
  • 485
  • 3
  • 12
1

Your problem is about the confusion on 'this' key.

When you use this in a function, it uses the context of that function.

So this is slightly using a different context:

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     this.data = data;
   });
}

than this:

Reader.prototype.makeAjaxCall = function(urlPath){
   var readerContext = this;
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     readerContext.data = data;
   });
}

and this is the solution for your problem.

cenk ebret
  • 687
  • 4
  • 15
1

In order to save the return data in a so called Class variable, given your example you need to understand the context where you are using the this keyword. In your example

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     this.data = data;
   });
}

this is referred to the Ajax object.

Since this.data in Reader object is pretty much encapsulated in the context of the Reader object you need to save it locally before making the Ajax call, in order to be able to access it from withing the call. Like this:

Reader.prototype.makeAjaxCall = function(urlPath){
    var _this = this;
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     _this.data = data;
   });
}

This pretty much solves the context problem and is very helpful when it comes to making function calls. A simple solution is to just define a local variable where you can store the data temporary and then use it to overwrite the the value of this.data Code should look like this:

 Reader.prototype.makeAjaxCall = function(urlPath){
        var tempData;
       //Make and Ajax call and return some value
       Ajax.success(function(data){
         tempData = data;
       });
       this.data = tempData;
    }
0

You could do something like

 Reader.prototype.searchData = function(param){
    var self = this;
    if (!this.data) {
       setTimeout(function() { self.searchData(param) }, 1000);
       return;
    }
    ...
 }
Sigfried
  • 2,943
  • 3
  • 31
  • 43
0

Try to change this:

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     this.data = data;
   });
}

to this:

Reader.prototype.makeAjaxCall = function(urlPath){
  var _this = this;

   //Make and Ajax call and return some value
   Ajax.success(function(data){
     _this.data = data;
   });
}
Undefitied
  • 747
  • 5
  • 14
0

One simple solution would be to put all the function calls within another function and only call that one from the callback:

var Reader = function(){
  //Initialize some variables
  this.data = null;
}

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   var that = this;
   Ajax.success(function(data){
     that.data = data;
     that.makeOtherCalls();
   });
};


Reader.prototype.makeOtherCalls = function(){

    // call the functions that need this.data here

};
pishpish
  • 2,574
  • 15
  • 22
0

You can use context setting of $.ajax() to set this at $.ajax() callback, deferred.then(); include error handling function within Reader instance.

// set `this` at `$.ajax()` to context of `Reader` instance
var Reader = function(context) {

  //Initialize some variables
  this.data = null;

  // handle `$.ajax()` errors
  this.err = function(jqxhr, textStatus, errorThrown) {
    console.log(textStatus, errorThrown, this);
  }

  this.makeAjaxCall = function(urlPath) {
    //Make and Ajax call and return some value
    return $.ajax({
        url: urlPath,
        // set `context` to `this`
        context: context || this,
      })
      .then(function(data) {
        this.data = data;
        console.log("in `Reader.makeAjaxCall`, this is:", this);
      }, this.err);
  }

  this.searchData = function(param) {
    //Needs access to this.data
    console.log("in `Reader.searchData`", this.data);
  }

  this.findData = function(id) {
    //Needs access to this.data
    console.log("in `Reader.findData`", this.data);
  }

  this.deleteItem = function(id) {
    // Needs access to this.data
    console.log("in `Reader.deleteItem`", this.data);
  }

}

var reader = new Reader();

reader.makeAjaxCall("")
.then(reader.searchData)
.then(reader.findData)
.then(reader.deleteItem)
.then(function() {
    console.log("complete", this.data, this)
})
guest271314
  • 1
  • 15
  • 104
  • 177
0

people are obsessed with using prototyping to create functions within a class, but there is a cleaner way, by using functions with the 'this' reference. Then you can have access to the inner workings of your class through a self variable.

eg:

var Reader = function(){
    var self = this;

    //Initialize some variables
    this.data = null;

    this.makeAjaxCall = function(urlPath){  
       Ajax.success(function(data){
         self.data = data;
       });
    }
}

Then in your prototype methods you can use the data with:

this.data

You can call the ajax method using:

this.makeAjaxCall(url);

And you can call the ajax method from outside of the class using:

var myReader = new Reader();
myReader.makeAjaxCall(url);

as per normal.

Neil H
  • 1
0

MAKE USE OF GLOBAL JS VARIABLE

You can simply make use of global JS variables for this kind of requirement. Please check the code below

var globalData; /* This is global.It can be accessed anywhere */
var Reader = function(){
  //Initialize some variables
  globalData = null;
}

Reader.prototype.makeAjaxCall = function(urlPath){
   //Make and Ajax call and return some value
   Ajax.success(function(data){
     globalData = data;
   });
}

Reader.prototype.searchData = function(param){
   //Access globalData here
}
Reader.prototype.findData = function(id){
  //Access globalData here
}

Reader.prototype.deleteItem = function(id){
  //Access globalData here
}
Thanga
  • 7,811
  • 3
  • 19
  • 38
  • Global variables are bad, especially if they are accessed by multiple instances of the same class. – dburner Mar 23 '16 at 13:25
  • Really? It is just increasing the scope. This is similar to any other object variable or a function variable – Thanga Mar 23 '16 at 15:11
  • Don't take my word for it, but read this: http://stackoverflow.com/questions/10525582/why-are-global-variables-considered-bad-practice-javascript – dburner Mar 23 '16 at 15:18
  • @dburner Nope. I disagree. It depends on how and how often you use global variables. This requirement takes only one global variable and it is fine. – Thanga Mar 23 '16 at 15:56
0

I have developed my own jQuery ajax wrapper for this, but it does require jQuery... If that helps check my github repo

JQPS Framework v. 1.1b - Github

This can help you a lot, it uses OOP by namespaces(the classes that you are looking for) like Myobject.vars.anything = value (from callback)

Example of call

Main.ajaxCall('auth','login','POST','TEXT',{dataObj},{Utils:{1:'setVar|Main,myVar,,callback',2:'anyOtherFunction'}},false);

This will trigger an ajax call to http://yoursite.com/auth/login with the dataObj as post object, at the callback the first function will be called, in our case the Utils.setVar() this will store the response from the callback in the Main.vars.myVar as we defined it in the callback object, after that the second function will be triggered and executed as defined in the call Utils.anyOtherFunction()... you can add how many functions you want in the callback.

For more information you can check the readme from github, this is set for sync and async calls but it does require jQuery, it does not work without it.

Sabbin
  • 2,215
  • 1
  • 17
  • 34