I have a tightly coupled javascript, where in there are series of if-else checks and multiple ajax calls are made. The ajax calls are nested type. My problem is I am in a deep nested ajax callable function and I want to get out from there gracefully.
The snippet of the code is .
function showSubscriptionLightBox() {
$.get("/ajax/get_subscription_lightbox_content.php?feed_id=" + feedid, function(data) {
//Work on the data we receive... and check whether user is logged in.
if(userLoggedIn) {
//Make one more ajax call
$.get("/ajax/is_user_subscribed.php?feed_id=" + feedid, function(data) {
//Work on data again.... and check if user is subscribed.
if(userSubscribed) {
//Then there is popup which comes up, a part of same page and it has a button name "task".
document.getElementById('task').onclick = function() {
if(document.getElementById('email_mode').checked) {
$.ajax({
url : "ajax/is_user_email_verified.php?user_id="+userID,
success : function(data) {
if(!data)
return;
var response;
response = eval("response = " + data);
if(!response)
return;
if(response['email_status'] == 0) {
//Exit from here
}}}
......
other part of code..
I want to exit gracefully from javascript, when the response['email_status'] == 0
Please tell me, how to do this??
I tried the return statement, but it took me to the enclosing function and not outside the script.
Thanks,
Amit
For what it is worth, here is some code from one of my applications. It syncs records using JSONP and AJAX. It first gets an array of object ids from a remote server. It then fetches the record for the object id at the zero index from the host server. Then it sends the record it receives to the remote server. At that point, it continues the process by starting the process with an incremented index into the array of ids. It terminates when the index reaches the end of the array.
(function( $ ) {
$.getJSON( 'http://remote.com/admin/record_ids.js?callback=?', function( data ) {
var set_record = function( index ) {
if ( index < data.length ) {
$.get( 'record_get.json', { contact_id: data[ index ] }, function( data ) {
$.getJSON( 'http://remote.com/admin/record_save.js?callback=?', data, function() {
set_record( index + 1 );
});
}, 'json');
}
};
set_record( 0 );
});
})( jQuery );
As you can see, when you want to get out gracefully, you just don't call. I can't imagine why you can't just return to stop your code.
There's a funny trick you can always use in JavaScript to escape the call stack: setTimeout(). It's useful in many situations, not just this, it is often used to work around DOM event related bugs in browsers as well.
$.ajax(
{
url: 'lol.php',
success: function(data)
{
setTimeOut(function()
{
// Your code comes here
}, 0); // 0 will ensure that it gets executed immediately
}
});
I know that with Prototype you could do this with try/catch blocks. You could throw an object from within one of the inner functions and it will travel up the call stack for other functions to intercept.
Related
I've been working on getting a function written to:
1) Process an input array using $.ajax calls to fill an output array (below this is inputList)
2) Below is what I have, but I'm having issues with it:
requestData(), when I call it, runs straight through to processing the outputList array without having fully populated/filled it - it puts one value into it then starts to process that, but the function still apparently runs on seperately to the subsequent processing asynchronously. I need it to be fully synchronous so that it does not return until the inputList array has been fully processed.
I'm not seeing the browser repainting the div that has its html updated on every call of the runajax() function - I'm attempting to do this with a setTimeout.
3) I've set the ajax request to be synchronous (async : false) - but this doesn't seem to help
I've tried to use jQuery's $.when to provide an ability to ensure that everything gets called in sequence - but clearly I'm not doing this correctly.
Would appreciate any help - I've asked previous related questions around this and had some useful help - but I've still not resolved this!
Thanks
//declare holding function requestData - expects a non-empty input data array named inputList
function requestData() {
//declare inner function runajax
function runajax() {
if(inputList.length > 0) {
//get first item from inputlist and shorten inputList
var data = $.trim(inputList.shift());
function getData() {
//send the data to server
return $.ajax({
url: 'sada_ajax_fetch_data.php',
cache: false,
async: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(data)
}
});
}
function handleReturnedData (response) {
response = $.trim(decodeURIComponent(response));
//update the div inner html
if(response == "Failed") {
$('#fetchupdatestatus').html('There was an error retrieving the data you requested!');
} else {
$('#fetchupdatestatus').html('The item returned was '+response);
}
//add the response from ajax to the end of the outputList array
outputList.push(response);
//set up the next ajax call
var doNextBitOfWork = function () {
runajax();
};
//call setTimeout so that browser shows refreshed div html
setTimeout(doNextBitOfWork, 0);
//return
return $.when();
}
//do the next ajax request and response processing
return getData().done(handleReturnedData);
} else {
//did the last one so return
return $.when();
}
}
//kick off the ajax calls
runajax();
}
var inputList = new Array();
var outputList = new Array();
.....load +/- 100 values to be processed using ajax into array inputList
requestData();
.....process stuff in array outputList
.....etc
There was my answer with "you're doing it wrong" earlier, but then I just decided to show, how you can do it (almost) right: https://jsfiddle.net/h4ffz1by/
var request_maker = {
working: false,
queue: [],
output: [],
requestData: function(inputList) {
if (request_maker.working == true) {
return false;
}
request_maker.output = [];
request_maker.working = true;
while (inputList.length > 0) {
var data = $.trim(inputList.shift());
request_maker.queue.push(data);
}
console.log(request_maker.queue);
request_maker.doTheJob();
return true;
},
doTheJob: function() {
current_data_to_send = request_maker.queue.shift();
console.log(current_data_to_send);
if (typeof current_data_to_send != 'undefined' && request_maker.queue.length >= 0) {
$.ajax({
url: '/echo/json/',
cache: false,
method: "post",
timeout: 2000,
data: {
requesttype: "getmydata",
email: encodeURIComponent(current_data_to_send)
},
success: function(data, status, xhrobject) {
console.log(xhrobject);
request_maker.handleReturnedData(data);
},
});
} else {
request_maker.working = false;
console.log('all data has been sent');
}
},
handleReturnedData: function(response) {
console.log(response);
response = $.trim(decodeURIComponent(response));
//response= 'Failed';//uncomment to emulate this kind of response
if (response == "Failed") {
$('#fetchupdatestatus').append('There was an error retrieving the data you requested!<br/>');
} else {
$('#fetchupdatestatus').append('The item returned was ' + response + '<br/>');
request_maker.output.push(response);
}
request_maker.doTheJob();
if (request_maker.working == false) {
console.log('all requests have been completed');
console.log(request_maker.output);
}
}
}
inputList = [1, 2, 3, 4, 5, 6];
if (request_maker.requestData(inputList)) {
console.log('started working');
}
if (!request_maker.requestData(inputList)) {
console.log('work in progress, try again later');
}
Note that I've changed request path to jsfiddle's ajax simulation link and replaced html() with append() calls to print text in div. The calls are made and get handled in the same order as it is in inputList, still they don't lock user's browser. request_maker.output's elements order is also the same as in inputList.
Have in mind, that you will need to add error handling too (probably just a function that pushes 'error' string into output instead of result), otherwise any ajax error (403/404/502, etc.) will get it "stuck" in working state. Or you can use complete instead of success and check request status right there.
UPD: Answer to the question: you cannot get both. You either use callbacks and let browser repaint inbetween asynchroneous requests or you make requests synchroneous and block browser untill your code finished working.
UPD2: There actually is some information on forcing redraw, however I don't know if it will work for you: Force DOM redraw/refresh on Chrome/Mac
I know my questions is marked as duplicate. But the given answer is using async:false. I don't want to force synchronous requests. How do maintain async ajax call sequence ???
I don't need to replace the content. I need to append svg one after another in a sequence.
I am appending 5 svg elements in a div. All svgs are coming by ajax call. The issue is the order of those svgs. Every time they appended in different order. I want to maintain their order. Please find below my code:
FlagRow.DEFAULTS = {
flagOrder: [
Enums.flagType.INDIA,
Enums.flagType.USA,
Enums.flagType.UK,
Enums.flagType.FRANCE,
Enums.flagType.GERMANY
]
}
var container = $(document.createElement("div"));
var topic = new Array();
for (var key in this.options.flagOrder) {
topic.push(this.options.flagOrder[key]);
}
var appendFlag = function (flag) {
console.log(flag);
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
container.append(svgDiv);
}
$.each(topic, function (i, val) {
$.when(//ajax call to get flag svg).done(function (flag ) { appendFlag(flag ); });
});
// api call to get flag svg
var deferred = $.Deferred();
$.ajax({
url: url,
type: 'get',
data: '',
dataType: 'xml',
timeout: 300000,
success: function (data) {
deferred.resolve(data);
},
error: function (e) {
console.log(':::error in flag:::', e);
},
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", 'myapikey');
}
});
Here every time flag svg comes in different order. I want it to display it in an order of enum. And so I tried it with $.when().done(). But it's working as per my requirement.
How do I maintain order of appended svgs coming via ajax call ???
You can use async: false to mimic what you tried to do with Deferred. Since you know the order at the moment of calling your ajax requests, using placeholders as the duplicate question (for some reason they re-opened this...) suggests is your best bet.
function getAllTheFlags() {
for( var i = 0; i < 5; i++ ) {
insertPlaceHolder( i ); //inserts <div id="placeholder-i"></div> at desired location
insertFlag( i );
}
}
function insertFlag( i ) {
$.ajax( { ... } ).success( function( data ) {
var svgDiv = $(document.createElement("div"));
$(svgDiv).addClass('svgDiv');
var importedSVGRootElement = document.importNode(flag.documentElement, true);
$(importedSVGRootElement).attr('viewBox', '0 0 100 125');
svgDiv.append(importedSVGRootElement)
$( '#placeholder-' + i ).replaceWith( svgDiv );
} );
}
The function insertFlag(..) is mandatory, as you need to copy the value of i.
You can not expect async ajax call to end in order of call. But you could wrap it in a function that takes the element as parameter that you can acces in your ajax callback.
function fetchContent(element, url){
$.ajax({
url: url,
success: function(data) {
element.whatever(...);
}
});
}
In your code you then create a div or search for an existent one. And call your fetchContent by passing that element as a parameter. Even if your ajax calls don't end in the order of call the content should be added to the good element.
I think it should work.
I have a big json data about 40000 item. When I send request to get all, browser is locked process until responce come.
So I am sending request by index and chunk like following.
var index = 0;
var chunk = 500;
var repeat = true;
document.getElementById('loading').style.display='inline-block';
while (repeat == true) {
var requestOptions = {
handleAs: "json",
sync: true,
query: {
page: index,
chunk: chunk
},
};
request.get("domain.com/getdata", requestOptions).then(
function(response) {
array.forEach(response.data, function(item) {
//do something
});
if (response.data.length < chunk) {
repeat = false;
document.getElementById('loading').style.display='inline-block';
}
index = index + 1;
},
function(error) {
repeat = false;
}
);
}
I am sending request to get first 500 record. Than get secont 500 record...
When I sart process, the browser locking. I want to Show loading request but not appearing.
I see in the comments on your question that you've been recommended to use async:true, to which you respond that it is sending requests without getting any response, and always sending the same request parameters.
I think then that you're perhaps a bit unfamiliar with the asynchronous paradigm in Javascript (remember, Ajax means asynchronous Javascript and XML).
First off: async:true is the right way to solve your problem. However, as you've noticed, that alone doesn't fix anything in your code.
Here's a simplified and modified version of your code (don't try this, it doesn't work, it's for explanation purposes only).
var index = 0;
var chunk = 500;
var repeat = true;
while (repeat == true) {
var requestOptions = {
handleAs: "json",
sync: false, // false is default, so this line is redundant
query: { page: index, chunk: chunk },
};
request.get("domain.com/getdata", requestOptions).then(
responseOk, responseError);
}
function responseOk(response) {
//do something..
if (response.data.length < chunk) {
repeat = false;
}
index = index + 1;
}
function responseError(error) {
repeat = false;
}
Here's the kicker: the ´responseOk´ function is never run. Therefore, index is never updated, and repeat is never set to false - in effect making your while loop infinite!
Why is this? The reason is that Javascript's "Ajax" functions (which are wrapped by dojo's request.get() and friends) are asynchronous.
What you are saying in your code (or rather, in my simplified version above) is effectively:
Hey, Javascript, do a GET request to the server. When you are done,
sometime in the future, run this responseOk function (or responseError
on error). In the mean time, while you are doing that, I'll
continue with my while loop.
So the while loop keeps churning out GET requests to the server, with the same index! Since the neverending loop is keeping your Javascript thread busy (you only have one!), the responseOk function isn't allowed to execute (even though the server may have responded).
That said, how can you split your huge JSON array into multiple, subsequent requests?
You can try something like this:
var index = 0,
chunk = 500,
requestOptions = {....};
function handleResponseAndGetNextChunk(response) {
response && array.forEach(response.data, function(item) {
//do something
});
if(response && response.data.length < chunk) {
return;
} else {
requestOptions.page = index++;
request.get("domain.com/getdata", requestOptions).then(
handleResponseAndGetNextChunk, responseError);
}
}
// To start off the sequence of requests:
handleResponseAndGetNextChunk(null);
The thing:
I have a page, which has to display undetermined number of images, loaded through AJAX (using base64 encoding on the server-side) one by one.
var position = 'front';
while(GLOB_PROCEED_FETCH)
{
getImageRequest(position);
}
function getImageRequest(position)
{
GLOB_IMG_CURR++;
$.ajax({
url: urlAJAX + 'scan=' + position,
method: 'GET',
async: false,
success: function(data) {
if ((data.status == 'empty') || (GLOB_IMG_CURR > GLOB_IMG_MAX))
{
GLOB_PROCEED_FETCH = false;
return true;
}
else if (data.status == 'success')
{
renderImageData(data);
}
}
});
}
The problem is that images (constructed with the renderImageData() function) are appended (all together) to the certain DIV only when all images are fetched. I mean, there is no any DOM manipulation possible until the loop is over.
I need to load and display images one by one because of possible huge number of images, so I can't stack them until they all will be fetched.
Your best bet would be to restructure your code to use async ajax calls and launch the next call when the first one completes and so on. This will allow the page to redisplay between image fetches.
This will also give the browser a chance to breathe and take care of its other housekeeping and not think that maybe it's locked up or hung.
And, use async: 'false' is a bad idea. I see no reason why properly structured code couldn't use asynchronous ajax calls here and not hang the browser while you're fetching this data.
You could do it with asynchronous ajax like this:
function getAllImages(position, maxImages) {
var imgCount = 0;
function getNextImage() {
$.ajax({
url: urlAJAX + 'scan=' + position,
method: 'GET',
async: true,
success: function(data) {
if (data.status == "success" && imgCount <= maxImages) {
++imgCount;
renderImageData(data);
getNextImage();
}
}
});
}
getNextImage();
}
// no while loop is needed
// just call getAllImages() and pass it the
// position and the maxImages you want to retrieve
getAllImages('front', 20);
Also, while this may look like recursion, it isn't really recursion because of the async nature of the ajax call. getNextImage() has actually completed before the next one is called so it isn't technically recursion.
Wrong and wrong. Don't user timers, don't chain them. Look at jQuery Deferred / when, it has everything you need.
var imgara = [];
for (image in imglist) {
imgara[] = ajax call
}
$.when.apply($, imgara).done(function() {
// do something
}).fail(function() {
// do something else
});
Try using setInterval() function instead of while().
var fetch = setInterval(loadImage, 2000);
function loadImage(){
position= new position; //Change variable position here.
getImageRequest(position);
if(!GLOB_PROCEED_FETCH){
clearInterval(fetch);
}
}
Javascript gurus, I need your help.
I need to compare two different arrays and check for different values. The values are coming from the same form multi select element. I tried getting a list of current values (cacheTermList) and checking for new value on change (newTermList). The idea is I want to pass an id to an ajax call if a new value was input, and return some data to the screen.
Code:
var cachedTermList = $('select#edit-categories').val();
if (cachedTermList == null) {
var cachedTermList = new Array();
}
$('select#edit-categories').chosen().change(function() {
var newTermList = $('select#edit-categories').val();
if (cachedTermList != null) {
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[i]);
alert(cachedTermList);
}
});
}
}
} else {
}
});
Bear with me, I don't tend to work with Javascript too often. I was trying to get a current list of values by setting cachedTermList on load, then when the select changes, set a newTermList to the new value of the field, then loop it, and check for a value in that list that is not in the cached list.
While I could see things happen, and dump both term lists and see different values, for the life of me I could not get it to push the found value to the cached list so that the next time the element changes, it doesn't keep sending the same value to the ajax call again and again. After the .push() executes, it just adds ',,,' without values. Where am I going wrong?
It is the typical closure - loop problem. All success callbacks reference the same i. At the time the callbacks are executed, the loop already finished and i will have the value newTermList.length + 1, so newTermList[i] will return undefined.
You have to capture the index or the value by introducing a new scope, which can be done by calling a function (JavaScript has no block scope).
Update: Another problem is that $.inArray does not return a boolean value, but the index of the element or -1. So you have to compare the return value against -1.
$('select#edit-categories').chosen().change(function() {
// ...
for(var i = 0; i < newTermList.length; i++) {
if ($.inArray(newTermList[i], cachedTermList) === -1) {
addTerm(newTermList[i], cachedTermList);
}
}
//...
});
function addTerm(term, target) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + term,
success: function(data){
//$('div#term-help-text').html(data);
target.push(term);
alert(target);
}
});
}
Also keep in mind that all Ajax calls will basically be executed at the same time. The loop does not wait until one call finished. If you want to execute the calls one at a time, you can use jQuery's Deferred objects.
You are using ajax in a for loop and pushing the newTermList item in its success handler. Since ajax is async by default the loop is not going to wait for the ajax request to get completed. Try to put the pushing code outside the ajax call. But this will not work if the ajax call fails, may be you dont want to add the item into cache when the ajax call fails.
Try something like this
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
context: i,//Here I am setting the value of i into context which can be used in the success handler using this keyword
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[this]);
alert(cachedTermList);
}
});
}
}