Functions within a loop using requirejs - javascript

I'm having an issue with calling functions within a loop across different modules using requirejs. The function call within the loop resides in module A and executes a function in module B that fires off an Ajax request using jQuery. Each iteration of the loop fires off a different request with different arguments being passed to module B's function that fires off the Ajax request. When the success function of the Ajax request executes, I find that all my argument values are always the values of the last Ajax call made, for all 4 separate Ajax calls.
I've done some googling and it sounds like this is a pretty common problem when executing a function within a loop. The fix tends to be to break out the function call into a different function, creating a different scope. Since my loop and Ajax calls are in 2 different modules I had assumed this would solve that issue, however it still persists.
I've tried some solutions in other stack overflow posts like:
JSlint error 'Don't make functions within a loop.' leads to question about Javascript itself and How to pass parameter to an anonymous function defined in the setTimeout call? without success. Anyone have any idea?
Sample code for loop module A:
define(["mpos"],
function(mpos){
var monitor = {
startMonitoring : function(poolObj){
// Start Monitoring
$.each(mpos.msgs, function(action,callback){
poolObj.action = action;
mpos.sendApiRequest(poolObj,action,callback);
});
}
};
return monitor;
}
);
Sample code for Ajax module B - this module is referenced as mpos in module A
define(["mule","constants"],
function(mule,constants){
var mpos = {
sendMessage : function(postData,callback,$poolOut){
return $.ajax({
'type':'post',
'url':constants.URLS.proxy,
'data':{'url':postData},
success : function(data){
// if we have $poolOut we know this is a mpos call
if($poolOut != undefined){
var keys = Object.keys(data);
// add poolOut to data
data.poolOut = $poolOut;
var poolObj = $poolOut.data('poolObj');
if(poolObj){
var action = poolObj.action;
console.log(poolObj,action);
if(action){
if(action == "getuserstatus"){
mule.registerPool(poolObj);
}
} else {
log.error("No action on poolObj while attempting to calculate the need for a registerPool call");
}
}
}
// parse data
callback.apply(this, data);
},
error : function(x,h,r){ ... },
dataType : 'json'
});
},
sendApiRequest : function(poolObj,action,callback){
var url = poolObj.url + '&page=api&action=' + action;
var $poolOut = constants.cache.monitorOutput.find('.pool-out.' + poolObj.id);
var dfd = mpos.sendMessage(url,callback,$poolOut);
$.when(dfd).always(function(){
var refreshTimer = setTimeout(function(){
if(constants.state.monitorEnabled){
mpos.sendApiRequest(poolObj, action, callback);
}
}, poolObj.refreshRate);
});
},
msgs : {
"getuserstatus" : function(data){ ... },
"getpoolstatus" : function(data){ ... },
"getuserworkers" : function(data){ ... },
"getuserbalance" : function(data){ ... }
}
};
return mpos;
}
);
Thanks!

NOTE: I am assuming that $poolOut.data('poolObj') is being used to find the poolObj instance passed in the call to startMonitoring, and will return the same instance each time.
You state, "Each iteration of the loop fires off a different request with different arguments being passed to module B's function that fires off the Ajax request."
This statement is not correct. Each iteration fires off a different request with the first argument poolObj being the same in each iteration.
In your .each iteration, you are overwriting the value of poolObj.action before each call to sendApiRequest.
In the AJAX success handler, which is likely invoked after all iterations have completed, the value of poolObj.action will have the value you set it to in the last iteration.
To solve this, I think you need to pass action as a parameter to sendMessage, too, so that a separate value is being stored in the closure for each function call.
var mpos = {
sendMessage : function(postData,action,callback,$poolOut){
return $.ajax({
'type':'post',
'url':constants.URLS.proxy,
'data':{'url':postData},
success : function(data){
// if we have $poolOut we know this is a mpos call
if($poolOut != undefined){
var keys = Object.keys(data);
// add poolOut to data
data.poolOut = $poolOut;
var poolObj = $poolOut.data('poolObj');
if(poolObj){
// action is not guaranteed to be the same as poolObj.action here,
// since poolObj.action may have changed since this function was first called
console.log(poolObj,action);
if(action){
if(action == "getuserstatus"){
mule.registerPool(poolObj);
}
} else {
log.error("No action on poolObj while attempting to calculate the need for a registerPool call");
}
}
}
// parse data
callback.apply(this, data);
},
error : function(x,h,r){ ... },
dataType : 'json'
});
},
sendApiRequest : function(poolObj,action,callback){
var url = poolObj.url + '&page=api&action=' + action;
var $poolOut = constants.cache.monitorOutput.find('.pool-out.' + poolObj.id);
var dfd = mpos.sendMessage(url,action,callback,$poolOut);
$.when(dfd).always(function(){
var refreshTimer = setTimeout(function(){
if(constants.state.monitorEnabled){
mpos.sendApiRequest(poolObj, action, callback);
}
}, poolObj.refreshRate);
});
},
msgs : {
"getuserstatus" : function(data){ ... },
"getpoolstatus" : function(data){ ... },
"getuserworkers" : function(data){ ... },
"getuserbalance" : function(data){ ... }
}
};

Related

jQuery object, inconsistent global variables and AJAX call

I'm looking to get what I thought would be a simple script to run an AJAX call and keep various values stored to an object, but I cannot get the globals to remain consistent the way I would expect.
I've gone around in circles trying what I think is everything. As soon as I put the AJAX call in I can't get it to play nicely with the global variables. The process value is always false that way and the content never loads in.
ExtContent = function(){
var self = this;
this.init = function() {
self.output = null;
self.process = false;
};
this.request = function(url){
$.ajax({
type : 'GET',
timeout : 10000,
dataType : 'html',
url : url,
passself : self,
success : function(response){
this.passself.setoutput(response);
},
error : function(req,response){
if(response==='error'){
self.error=req.statusText;
}
}
});
};
this.setoutput = function(data){
this.output = data;
this.process = true;
};
this.returnprocess = function(){
return self.process;
};
this.returnoutput = function(){
return self.output;
};
self.init();
};
<div id="holder"></div>
loadcontent = new ExtContent();
loadcontent.request('/test.html');
if(loadcontent.returnprocess()){
$('#holder').before(loadcontent.returnoutput());
}else{
$('#holder').before('FAILED');
}
I can't get the process to be true and the content to be stored in output.
Thanks.
Despite wrapping everything as a class/object, the jQuery $.ajax call is still an asynchronous operation. basically "You have ordered a pizza, then try to eat it before it arrives".
i.e. this orders it:
loadcontent.request('/test.html');
and this tries to eat it immediately:
if(loadcontent.returnprocess()){
The call to setoutput (i.e. the "Pizza delivery") happens long after these operations complete.
You need to add event handler properties to your class, or use deferreds+promises to wait for the data to arrive.
To use promises, just return the $.ajax result from request:
this.request = function(url){
return $.ajax({
type : 'GET',
timeout : 10000,
dataType : 'html',
url : url,
passself : self,
success : function(response){
this.passself.setoutput(response);
},
error : function(req,response){
if(response==='error'){
self.error=req.statusText;
}
}
});
};
and use it like this:
loadcontent.request('/test.html').done(function(){
if(loadcontent.returnprocess()){
$('#holder').before(loadcontent.returnoutput());
}else{
$('#holder').before('FAILED');
}
});
Or if you setup the return values correctly inside request:
loadcontent.request('/test.html').done(function(){
$('#holder').before(loadcontent.returnoutput();
}).fail(function(){
$('#holder').before('FAILED');
});
Maybe this can help you
this.setoutput = function(data){
// 'this' here, is refering 'setoutput' function, not ExtContent,
// so ExtContent.process != ExtContent.setoutput.process
// this.output = data;
// this.process = true;
self.output = data;
self.process = true;
};

JavaScript OOP - Not able to set the public value from Ajax call inside a Class structure

i faced issue while writing a class specially for handling Ajax calls & its response.
Purpose of below code : Based on user records exist in database, i need to set the flag "isPersonalDivRequiredFlag" for further use.
// New ajax request class
function ajaxRequestHandler(){
this.needOutPutInJSON = 0;
this.url = "/libraries/ajaxHelper.php";
this.requestType = "POST";
this.isPersonalDivRequiredFlag = 0;
};
// Methods written for Class ajaxRequestHandler
ajaxRequestHandler.prototype = {
sentRequest : function(userData, typeID){
var options = {type: this.requestType, context: this, url: this.url, data: { data: userData, type: typeID }};
if(this.needOutPutInJSON){
options.dataType = "json";
}
// Check whether user want to see the response
options.success = function(rec){
if(rec == "1"){
this.isPersonalDivRequiredFlag = 1;
}
};
//Jquery Ajax
$.ajax(options);
},
enableJSONoutput : function(){
this.needOutPutInJSON = 1;
},
getFlagValue : function(){
return this.isPersonalDivRequiredFlag;
},
setFlagValue : function(){
console.log('Setflag Func called.');
this.isPersonalDivRequiredFlag = 1;
}
};
And i use the code as below.
var newRequest = new ajaxRequestHandler();
console.log('Before change [Value of isPersonalDivRequiredFlag variable] : ' + newRequest.getFlagValue()); // Output 0
newRequest.sentRequest({}, "recordExist");
console.log('After change [Value of isPersonalDivRequiredFlag variable] : ' + newRequest.getFlagValue()); // Output 0
And when i set the flag "isPersonalDivRequiredFlag " to 1 inside the Success method of Ajax call but its unable to retain this value when it will be accessed through its own method "getFlagValue" function.
The whole piece of code will work fine if i remove Ajax call function & made it a normal prototype method. So i know the cause but not able to find any solution :(
what the things i tried already?
I found some techniques people suggested while Google it over internet.
a) Used this configuration inside Ajax call but no luck :(
context:this
b) inside Success method :
var myclass = this;
And called the prototype function as below
myclass.setFlagValue();
but no luck :(

Javascript Backbone Storing values in higher context

I'm using backbone and I have this code:
render: function() {
var displayData = this.model;
fetchData('checkComplete', this.model.id, function(checkCompleteData) {
displayData.complete = checkCompleteData; // Value is true
});
fetchData('showGraphicAssets', this.model.id, function(showGraphicsData) {
$.each(showGraphicsData, function(index, value) {
if (value.type === "GRAPHIC_A") {
displayData.assetId = value.id; // Value is 808
}
});
});
console.log(displayData.complete); // Valus is undefined
console.log(displayData.assetId); // Value is undefined
var html = this.template.tmpl(displayData.toJSON());
$(this.el).html(html);
}
fetchData = function(dataRequest, id, callback) {
request = '/show/' + id + '/completed.json';
$.ajax({
url: '/app' + request,
type: 'GET',
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
As you can see, I have the variable displayData defined outside of the fetchData functions. When I try to add properties inside the fetchData context, it works, and when I try to use it outside the fetchData context, it results to undefined. How can I fix this?
It is the most common problem people have with JavaScript. You do not honor the asynchronous nature of your callbacks.
You can not set a value in a callback function (like you do with displayData.assetId) and assume that it must be available a few lines later. It's not. It will never be.
It will be available when the callbacks are done. Since you have two of them, use jQuery's $.when()function (and its companions .then(), .fail() and .always()) which have been made specifically for handling multiple asynchronous calls:
render: function () {
var self = this, model = self.model, template = self.template;
$.when(
$.get('/app/show/checkComplete/' + model.id + '/completed.json'),
$.get('/app/show/showGraphicAssets/' + model.id + '/completed.json')
)
.then(function (checkCompleteData, showGraphicsData) {
// this will receive the response data in the correct order
model.complete = checkCompleteData;
$.each(showGraphicsData, function (index, value) {
if (value.type === "GRAPHIC_A") {
model.assetId = value.id;
return false; // break the $.each()
}
});
$(self.el).html( template.tmpl(model.toJSON()) );
})
.fail(function () {
// this will be called if either HTTP request fails
// also check out the arguments and react accordingly to errors
});
}
Note how the code also gets shorter as you need less boilerplate.
If you've made sure that your views use the correct Content-Type (application/json) then $.get() is enough; avoiding $.ajax() is visually much cleaner.

Accessing outer scope

I'm working on creating a Users collection with the ability to then grab single users inside. This will be used to match from another system, so my desire is to load the users once, and then be able to fine/match later. However, I'm having a problem accessing the outer users collection from an inner method.
function Users(){
var allUsers;
this.getUsers = function () {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
allUsers = data;
}
});
return allUsers;
};
this.SingleUser = function (name) {
var rate = 0.0;
var position;
this.getRate = function () {
if(position === undefined){
console.log('>>info: getting user position to then find rate');
this.getPosition();
}
$.ajax({
url: '../app/data/rates.json',
async: false,
dataType: 'json',
success: function(data) {
rate = data[position];
}
});
return rate;
};
this.getPosition = function () {
console.log(allUsers);
//position = allUsers[name];
return position;
};
//set name prop for use later I guess.
this.name = name;
};
}
and the test that's starting all of this:
it("get single user's position", function(){
var users = new Users();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
The getPosition method is the issue (which might be obvious) as allUsers is always undefined. What I have here is yet another attempt, I've tried a few ways. I think the problem is how the Users.getUsers is being called to start with, but I'm also unsure if I'm using the outer and inner vars is correct.
Though the others are correct in that this won't work as you have it typed out, I see the use case is a jasmine test case. So, there is a way to make your test succeed. And by doing something like the following you remove the need to actually be running any kind of server to do your test.
var dataThatYouWouldExpectFromServer = {
bgrimes: {
username: 'bgrimes',
show: 'chuck',
position: 'mgr'
}
};
it("get single user's position", function(){
var users = new Users();
spyOn($, 'ajax').andCallFake(function (ajaxOptions) {
ajaxOptions.success(dataThatYouWouldExpectFromServer);
});
users.getUsers();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
This will make the ajax call return whatever it is that you want it to return, which also allows you to mock out tests for failures, unexpected data, etc. You can set 'dataThatYouWouldExpectFromServer' to anything you want at any time.. which can help with cases where you want to test out a few different results but don't want a JSON file for each result.
Sorta-edit - this would fix the test case, but probably not the code. My recommendation is that any time you rely on an ajax call return, make sure the method you are calling has a 'callback' argument. For example:
var users = new Users();
users.getUsers(function () {
//continue doing stuff
});
You can nest them, or you can (preferably) create the callbacks and then use them as arguments for eachother.
var users = new Users(), currentUser;
var showUserRate = function () {
//show his rate
//this won't require a callback because we know it's loaded.
var rate = currentUser.getRate();
}
var usersLoaded = function () {
//going to load up the user 'bgrimes'
currentUser = new users.SingleUser('bgrimes');
currentUser.getRate(showUserRate);
}
users.getUsers(usersLoaded);
your approach to fill the data in allUsers is flawed
the ajax call in jquery is async so every call to users.getAllUsers would be returned with nothing and when later the success function of the jquery ajax is called then allUsers would get filled
this.getUsers() won't work. Its returning of allUsers is independent from the ajax request that fetches the data, because, well, the ajax is asynchronous. Same with getRate().
You'll have to use a callback approach, where you call getUsers() with a callback reference, and when the ajax request completes, it passes the data to the callback function.
Something like:
this.getUsers = function (callback) {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
And the call would be along the lines of:
var user_data = null;
Users.getUsers(function(data) {
user_data = data;
});

How can I stop an object method call before the ajax has completed

I have a the following java script object
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
I was wondering if there is a way stop some one calling the eventTypeObj.getEventTypeIds(); before the ajax call in the constructor has succeeded, and there is no data in the allEventTypes array?
Something like this would be way better (im not guaranteeing this is 100% working, but the concept is sound):
function eventTypeObj() {
this.allEventTypes = [];
this.hasLoadedEventTypes = false;
var loadEventTypes = function(cb) {
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
this.allEventTypes = eval("(" + result.d + ")");
this.hasLoadedEventTypes = true;
cb();
}
});
};
this.getEventTypeIds = function(updateEventTypes, callback) {
var _getEventTypeIds = function() {
var eventTypeIDs = [];
for (var i = 0; i < this.allEventTypes.length; i++) {
eventTypeIDs.push(this.allEventTypes[i].Id);
}
return eventTypeIDs;
};
if (!this.hasLoadedEventTypes || updateEventTypes) {
loadEventTypes(function(){ callback(_getEventTypeIds()); });
}
else callback(_getEventTypeIds());
};
}
Example usage:
var eto = new eventTypeObj();
eto.getEventTypeIds(false, function(eventTypeIdArray) {
// do stuff with the id array
});
/*
somewhere later on you want to get an updated eventTypeId array
in case the event types have changed.
*/
eto.getEventTypeIds(true, function(eventTypeIdArray) {
// do stuff with the updated ids
});
var allowCall = false;
function eventTypeObj() {
allEventTypes = [];
// When the object is created go and get all the event types that can be included in journey or clusters.
$.ajax({
url: "/ATOMWebService.svc/GetDisplayEventTypes",
dataType: "json",
success: function(result) {
allEventTypes = eval("(" + result.d + ")");
allowCall = true;
}
});
// Returns a list of all the event type IDS.
this.getEventTypeIds = function() {
if(!allowCall) return; // or pop up a message
var eventTypeIDs = [];
for (var i = 0; i < allEventTypes.length; i++) {
eventTypeIDs.push(allEventTypes[i].Id);
}
return eventTypeIDs;
};
}
Or just check if allEventTypes is empty or not.
There is no way to prevent someone from calling it too soon. What would you want to have happen if they call it too soon?
It looks like your code now currently returns an empty array if allEventTypes hasn't yet been filled in. You can decide whether the empty array is the right result or if you should throw an exception when it's called too early to make it absolutely clear to the caller that the data is not yet available.
You could provide some helper code for people who need that information, but it might not yet be available. For example, you could allow them to register a callback that would get called from the success handler after the data had been filled in. You could allow them to query whether the data is available yet.
If you don't want the responsibility for the timing to be on the callers, then you cannot offer a synchronous way to get this information. Instead, you would only offer a callback mechanism for getting the data. If the data is ready, the callback would get called immediately. If the data is not ready, the callback would get called when the ajax function completes. In either case, the caller would have to process the data in the callback only and getEventTypeIds would not be a normal call to get the data like it is now, but rather a call to register a callback that would be called with the data when was ready. This would relieve the caller from having to know implementation details of when the data was ready, but would force them to use the asynchronous nature of the callback mechanism.
this.getEventTypeIds = function(callback) {
if (allEventTypes.length > 0) {
// data is ready call the callback with the data now
} else {
// store the callback to be called later from the success handler
}
}
You can check if the eventType array is empty, right?
if(allEventTypes.length == 0)
{
return;
}

Categories