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
.
13 Answers 13
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
Comments
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(...);
}
}
Comments
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
Comments
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
}
Comments
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.
Comments
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;
}
Comments
You could do something like
Reader.prototype.searchData = function(param){
var self = this;
if (!this.data) {
setTimeout(function() { self.searchData(param) }, 1000);
return;
}
...
}
Comments
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;
});
}
Comments
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
};
Comments
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.
Comments
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
}
4 Comments
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.
Comments
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)
})
this
inAjax.success(function(data){this.data = data;});
works. You best use arrow functions,self = this;
orbind(this);