How to continue calling getjson untill it has no empty response? - javascript

Hi all i got a getjson call and wondering how i can check its response(siteContents) if it is empty or if it doesn't have a required string(for example look for seasonEpisode=)then call getjson again .Can we call getjson itself from within it ?My goal is to get correct response from getjson.Hope you guys help me.Thanks
$.getJSON('http://www.mysite.com/doit.php?value=55?', function(data){
//$('#output').html(data.contents);
var siteContents = data.contents;

Try this:
var handler = function(data){
//$('#output').html(data.contents);
var siteContents = data.contents;
if (!siteContents) {
$.getJSON('http:/...', handler);
return;
}
// handle siteContents
}
$.getJSON('http://...', handler);
edit: the above would spam the server with repeating attempts in case the siteContents is empty - creating infinite loop and high load. I would suggest two improvements:
1) count how many repeating empty siteContents loops you made. Cancel the loop with an error message (if appropriate) after some failure threshold (eg. 20 attempts).
2) do the iteration with setTimeout(function() { $.getJSON(...) }, delay) where delay is some milliseconds to wait between retries.

It sounds like the better question is why doesn't your server return the 'correct' response on the first try? Or as NuclearGhost points out, why does it return different responses for the same request?
But to accomplish what you're asking for requires recursion. You can't just do it in a loop because the response is asynchronous. But if you name a function, you can call that function in the success handler, something like this:
function getJSONRecursive(maxRetries, count) {
if(!count) count = 1;
if (count > maxRetries) {
alert('Giving up after '+count+' retries.');
return;
}
$.getJSON('http://www.mysite.com/doit.php?', function(data) {
if(!data || !data.contents || data.contents.indexOf('seasonEpisode') == -1) {
getJSONRecursive(++count, maxRetries);
} else {
$('#output').html(data.contents);
}
})
}
Then invoke it like this:
getJSONRecursive(5);
I wouldn't recommend doing without the count, because otherwise you will overflow the stack if the correct response never comes back. If the situation you're avoiding is a server timeout or overload problem, then I would recommend putting the recursive call in a timeout, like so:
if(!data || !data.contents || data.contents.indexOf('seasonEpisode') == -1) {
setTimeout(function() { getJSONRecursive(++count, maxRetries)}, 5000);
// etc.
This waits an extra 5 seconds between calls, but the extra time will help ensure your server doesn't get slammed and your getjson calls don't just run themselves out too quickly.

Related

How can I ensure an if loop waits for and receives a result before running the next if loop in sequence

I have a javascript function where if loops are not following each other sequentially. I need them to run one after the other. The second loop should not run until the first loop has finished, because it deals with the output of the first.
I use an if loop (loop 1) to call a function in a child iframe (this frame contains mapping elements, and I can't reasonably combine it with the parent frame). This part is working as intended.
The function in the iframe is triggered, and it makes a call to an external service, and awaits a response. When it receives a response it passes either "error" or "ok" back to the parent function by using the 'return' function. This part is working as intended.
The parent receives the response, sets a variable and then should continue on with the next if statement (loop 2) that does something else.
What actually happens is that loop 1 runs, then loop 2 also runs, and loop 2 returns results before loop 1 - which screws things up as loop 2 is meant to be dealing with thee results from loop 1.
jQuery(document).on('click', '.gv-button-delete', function (e) {
e.preventDefault(); //prevent submission
console.log('Intercepted delete button request');
var delLoc = "";
(function ($) {
var delLoc2 = $('.gv-button-delete').attr("href"); //capture default action, because we need the time-valid nonce
delLoc = delLoc2;
}(jQuery));
var objID = document.getElementById("input_4_40").value;
objID = parseInt(objID);
var iframe = document.getElementById("MapFrame1");
var result = "";
if (iframe) { //1st if loop that collects the answer
var iframeContent = (iframe.contentWindow || iframe.contentDocument);
var result = iframeContent.WriteLog(objID); //call the function from the iframe, and hopefully wait for a result.
console.log(result);
}
if (result == "error") { //the second if loop
console.log("Step 5")
console.log("There was an error with removing the feature");
} else if (result == "ok") {
console.log("Step 5")
console.log("The spatial delete completed correctly");
} else {
console.log("Step 5")
console.log("unexpected result of spatial delete")
}
});
the iframe code, becuase it's useful for context.
function WriteLog(objID){
var retireFeatureID = hotspots.getFeature(objID);
var successStatus = "";
retireFeatureID.feature.properties["SystemStatus"] = "Deleted";
hotspots.updateFeature(retireFeatureID.feature, function (err, response) { //This is a syncronous call to another service
if (err) {
console.log("Step 3")
console.log(err);
successStatus = "error";
console.log("successStatus is: " + successStatus);
} else {
console.log("Step 3")
console.log(response);
successStatus = "ok";
console.log("successStatus is: " + successStatus);
}
});
console.log("Step 4")
console.log("Updated the status of feature: " + objID);
console.log("child iframe has variable successStatus as: " + successStatus);
return successStatus;
}
What actually happens is that the console results look like:
Step 4
Step 5
Step 3
The second loop is returning before the first loop has finished and returned a result.
async-await might the answer to your problem.
Here is how it works.
You define a function that sends a response with some delay (maybe because of a network call or something).
async function f() {
// Make network call and return value
return value;
}
And you call this function with an await.
var valueRequired = await f();
if(valueRequired == something) {
doSomeWork();
}
I hope this was clear.
Reference: MDN
Do note that this is not compatible in older browsers, as this is a rather modern JS construct.
This might be due to how callbacks and the javascript event loop work in general, and steps 4 and 5 will be executed first before step 3.
The function response callback will be placed at the end of call stack, which causes the remaining code (step 4 onwards and second if loop) to be executed without waiting for the callback to complete despite the other service code being synchronous.
I would suggest you to either convert the service function to one with a direct return and if possible not using callbacks, or changing the WriteLog function to a callback function by adding a callback argument and invoking it once you get the response from the other service.
JavaScript Event Loop Explained
Why don't you add a flag. This flag can go right after console.log(result). The 2nd if block can be inside a while that does not allow for the code to proceed before this flag is true. This ensures that your 2nd if won't happen before the 1st.

jQuery/AJAX set timeout when rate limit of 3rd party API is reached

In my app I make several nested AJAX calls to the LiquidPlanner API that limits requests to 30 requests every 15 seconds. When I hit the limit, I want to set a timeout of sorts to stop sending requests to the API until the 15 seconds have elapsed. This (at the moment) will only be used by one person ever, so multiple clients are not a concern.
Upon hitting the rate limit the response is:
{
"type":"Error",
"error":"Throttled",
"message":"34 requests, exceeds limit of 30 in 15 seconds. Try again in 7 seconds, or contact support#liquidplanner.com"
}
Here is some code, simplified for brevity:
$.getJSON('/dashboard/tasks/123, function(tasks) {
$.each(tasks, function(t, task) {
$.getJSON('/dashboard/project/987, function(project) {
$.getJSON('/dashboard/checklist-items/382983, function(checklist-items) {
// form some html here
});
});
});
});
So at any point in this process I could hit the limit and need to wait until the timeout has completed.
I am also open to suggestions to better form the requests instead of nesting them.
Another solution that probably prevents hammering better is a queue - however you need to be aware that the order of requests could be significantly different using this method. And that only one request will ever run at a time (so total response times may increase significantly depending on the use case).
//Keep track of queue
var queue = [];
//Keep track of last failed request
var failed_request = false;
function do_request(url, callback) {
//Just add to queue
queue.push({
url:url,
callback:callback
});
//If the queue was empty send it off
if (queue.length === 1) attempt_fetch();
}
function attempt_fetch() {
//If nothing to do just return
if (queue.length === 0 && failed_request === false) return;
//Get the url and callback from the failed request if any,
var parms;
if (failed_request !== false) {
parms = failed_request;
} else {
//otherwise first queue element
parms = queue.shift();
}
//Do request
$.getJSON(parms.url, function(response) {
//Detect throttling
if (response.type === 'error' && response.error === 'throttled') {
//Store the request
failed_request = parms;
//Call self in 15 seconds
setTimeout(function(){
attempt_fetch();
}, 15000);
} else {
//Request went fine, let the next call pick from the queue
failed_request = false;
//Do your stuff
parms.callback(response);
//And send the next request
attempt_fetch();
}
}
}
...your logic still remains largely unchanged:
do_request('/dashboard/tasks/123', function(tasks) {
$.each(tasks, function(t, task) {
do_request('/dashboard/project/987', function(project) {
do_request('/dashboard/checklist-items/382983', function(checklist_items) {
// form some html here
});
});
});
});
Disclaimer: Still completely untested.
As far as design patterns for chaining multiple requests, take a look at the chaining section in the following article: http://davidwalsh.name/write-javascript-promises . Basically, you could create a service that exposes a method for each type of request, which returns the promise object and then chain them together as needed.
As far as you question about setting a timeout, given the information you provided, it is a bit difficult to advice you on it, but if that is absolutely all we have, I would create a request queue ( a simple array that allows you to push new requests at the end and pop the from the head ). I would then execute the known requests in order and inspect the response. If the response was a timeout error, set a timeout flag that the request executor would honor, and if successful, either queue additional requests or create the html output. This is probably a pretty bad design, but is all I can offer given the information you provided.
Write a wrapper that will detect the rate-limited response:
//Keep track of state
var is_throttled = false;
function my_wrapper(url, callback) {
//No need to try right now if already throttled
if (is_throttled) {
//Just call self in 15 seconds time
setTimeout(function(){
return my_wrapper(url, callback);
}, 15000);
}
//Get your stuff
$.getJSON(url, function(response) {
//Detect throttling
if (response.type === 'error' && response.error === 'throttled') {
/**
* Let "others" know that we are throttled - the each-loop
* (probably) makes this necessary, as it may send off
* multiple requests at once... If there's more than a couple
* you will probably need to find a way to also delay those,
* otherwise you'll be hammering the server before realizing
* that you are being limited
*/
is_throttled = true
//Call self in 15 seconds
setTimeout(function(){
//Throttling is (hopefully) over now
is_throttled = false;
return my_wrapper(url, callback);
}, 15000);
} else {
//If not throttled, just call the callback with the data we have
callback(response);
}
}
}
Then you should be able to rewrite your logic to:
my_wrapper('/dashboard/tasks/123', function(tasks) {
$.each(tasks, function(t, task) {
my_wrapper('/dashboard/project/987', function(project) {
my_wrapper('/dashboard/checklist-items/382983', function(checklist_items) {
// form some html here
});
});
});
});
Disclaimer: Totally untested - my main concern is the scope of the url and callback... But it's probably easier for you to test.

Javascript rendering. How to write Javascript so it doesn't continue executing code before a function call has ended

Not sure if my question is subjective/objective but as a JavaScript newbie i'm encountering this problem quite a lot. So here I go.
I'm used to write C#, so my JavaScript structure looks like C#. And just that, that gives problems I think ;-)
Let's give a simple example where I met my problem again today:
MyLibrary.fn.InitAddEntityForm = function () {
$('a#btnAddEntity').click(function () {
//post data and receive object with guid and isPersisted boolean
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png");
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
};
//////*****/////
//SOME FUNCTION THAT SENDS MY FORM AND RETURNS AN OBJECT WITH TRUE VALUE AND POSTED ENTITY ID
/////*****//////
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
}, "json"
);
return persistedObject;
};
Okay, thats it. Everything looks okay right? Browser says no.
I tried to debug it using firebug, looping over my code line by line, and that way the browser does what I want: Execute a new function to show the next panel in my wizard.
After placing a lot of Console.logs() in my code I figured out that this must be something about timing in JavaScript. In C# the code executes line by line, but apparently JavaScript doesn't.
By placing that Console.log("test") I noticed that "test" appeared in my console before "Post status: Success!".
So here's my question, how should I write my JavaScript code so I have control over the way the browser executes my code?
Should I really replace the code below to the end of my CheckAndSendAddEntityForm()?
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("fout");
}
Is this how I have to write JavaScript: One big domino effect or am I just doing something wrong?
$.post is a shortcut for an AJAX call, AJAX is by definition asynchronous, which means it won't wait on a response before continuing processing. If you switch it to a regular AJAX() method, there is an async option you can set to false, which will make it behave as you are expecting.
Alternatively you can also define a function to execute on successful return of the AJAX request, in which you can call the next step in your process chain.
The AJAX call is asychronous; that means that the callback method exposes by $.post will be executed when the request completes, but your javascript will continue executing as soon as the invoke to $.post finishes. If you want to do something after the ajax call is done, you need to provide a callback method and do something else, ex:
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl, callback) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
callback(); // This is where you return flow to your caller
}, "json"
);
};
Then you invoke like so:
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png", function()
{
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject .gdPronoId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
JavaScript is single-threaded. If you have asynchronous functionality, a simple boolean semaphore variable will help not to allow invocations of a function while some processes are running.
If you want to execute asynchronous tasks one by one (like a domino line), you will need to use callback functions.
What you're encountering is the "asynchronous" bit of AJAX. If you want to physically (as in the line line by line in the Javascript file) you can use the .success,.pipe or .done jQuery methods to add a callback to process the data further. Don't embed your callbacks if you can help it, or you will get a "domino effect" as you call it.

Javascript recursive setTimeout doesn't use full final JSON returned

I am making recursive calls to a URL until it returns success or has hit the max tries limit. Here is the relevant code, (minified, so to speak):
function doSomething(numRetries) {
$.post('someURL', {retry: numRetries},
function (data) {
if (data.value == 1) {
displayResults(data.message, data.value);
} else if (data.value == "retry") {
setTimeout( function() { doSomething(data.retries) }, 1000);
} else {
displayResults(data.message, data.value);
}
},
"json"
);
}
IFF the first call to sumeURL returns data.value == 1, it executes displaySuccess. Similarly, if it returns another value (e.g. 0), it will displayFailure() successfully.
The problem lies in the recursive part. After it kicks off the retries, it does call doSomething() again with an incrementing retries value, but any return data after that is not used.
So when my retry timeout inside someURL is 3, for example, I can see in firebug:
post('someURL', 0) returns JSONified (value = "retry", retries = 1)
post('someURL', 1) returns JSONified (value = "retry", retries = 2)
post('someURL', 2) returns JSONified (value = 0, error = "Display this error!")
but an alert() inside displayFailure indicates that error = [undefined], even though value = 0 (not "retry"). Firebug indicates proper JSON parsing is occurring.
EDIT modify the doSomething to be a more accurate reflection of reality, though the changes shouldn't introduce any uncertainty, and by request, here are the actual return values from the post calls:
{"success":"retry","retryCount":"1"}
{"success":"retry","retryCount":"2"}
{"success":0,"errormsg":"The request is taking longer than expected, but should be completed soon. Please try again in 15 minutes."}
and lastly here is a minified displayResults():
function displayResults(text, status) {
$('#dispElem').queue(function(next) { //this is so that fades happen around the text update, not before/during it; there may be better ways to do this
$('#dispElem').html(text);
if (status == 1) {
$('#dispElem').addClass("success");
} else {
// hide and show random elements
}
next();
}).fadeIn().queue(function(next) { //scroll to bottom; next(); });
}
Arrrgh.
The worst scenario of all - an uppercase/lowercase mistake I just kept overlooking until forced to retype to minify.
Thanks, all, for the comments and making me recomb it, with a finer tooth, so to speak.

JQUERY AJAX ---- Pausing for usability reasons but only when nessesary?

I have a LoadingStatus Function that has two options SHOW or HIDE.
The Show triggers to display when the JQUERY POST is made, the HIDE happens after the RESPONSE comes back.
The issue I'm having is that sometimes this happens so fast that it makes for a bad experience. What I thought about doing was putting in a JavaScript PAUSE, but if the POST takes a while to respond it will take even longer because of the PAUSE.
How can I have my SHOW HIDE function work together, to make sure at minimum the SHOW was displayed to the user for at least 1/2 second?
function saveBanner (action) {
if (action == 'show') {
// Display the AJAX Status MSG
$("#ajaxstatus").css("display","block");
$("#msg").text('Saving...');
}
else if (action == 'hide') {
$("#ajaxstatus").css("display","none");
$("#msg").text('');
}
};
Thanks
In your ajax success callback, you can put the hide command in a setTimeout() for 1500 miliseconds:
success: function(results) {
setTimeout(function(){
saveBanner("hide");
}, 1500);
}
Of course that would merely add 1.5 seconds onto however long the process itself took. Another solution would be to record the time the process started, with the Date object. Then, when the callback takes place, record that time and find the difference. If it's less than a second and a half, set the timeout for the difference.
/* untested */
var start = new Date();
success: function(results) {
var stop = new Date();
var difference = stop.getTime() - start.getTime();
difference = (difference > 1500) ? difference : 1500 ;
setTimeout(function(){
saveBanner("hide");
}, difference);
}
You can perform this math either inside your callback, or within the saveBanner() function itself, within the show portion you would set the starting time, within the hide() portion you would check the difference and set the setTimeout().
You can use setTimeout/clearTimeout to only show the status when the response takes longer than a set amount of time to load.
Edit:
Some untested code:
var t_id = 0;
function on_request_start()
{
t_id = setTimeout(show_message, 1000);
}
function on_request_completed()
{
clearTimeout(t_id);
hide_message();
}
The JQuery handlers should look something like the above. The message will not be shown if you receive a reply in less than a second.
var shownTime;
function saveBanner (action) {
if (action == 'show') {
// Display the AJAX Status MSG
$("#ajaxstatus").css("display","block");
$("#msg").text('Saving...');
shownTime = new Date().getTime();
}
else if (action == 'hide') {
var hideIt = function() {
$("#ajaxstatus").css("display","none");
$("#msg").text('');
};
var timeRemaining = new Date().getTime() - shownTime - 1500;
if (timeRemaining > 0) {
setTimeout(hideIt, timeRemaining);
else {
hideIt();
}
}
};
As of jQuery 1.5, you are able to extend the $.ajax functionality by using prefilters. I wanted a similar experience where a message was shown a minimum amount of time when an ajax call is made.
By using prefilters, I can now add a property to the ajax call named "delayedSuccess" and pass it a time in milliseconds. The time that is passed in is the minimum amount of time the ajax call will wait to call the success function. For instance, if you passed in 3000 (3 seconds) and the actual ajax call took 1.3 seconds, the success function would be delayed 1.7 seconds. If the original ajax call lasted more than 3 seconds, the success function would be called immediately.
Here is how I achieved that with an ajax prefilter.
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (originalOptions.delaySuccess && $.isFunction(originalOptions.success)) {
var start, stop;
options.beforeSend = function () {
start = new Date().getTime();
if ($.isFunction(originalOptions.beforeSend))
originalOptions.beforeSend();
};
options.success = function (response) {
var that = this, args = arguments;
stop = new Date().getTime();
function applySuccess() {
originalOptions.success.apply(that, args);
}
var difference = originalOptions.delaySuccess - (stop - start);
if (difference > 0)
setTimeout(applySuccess, difference);
else
applySuccess();
};
}
});
I first check to see if the delaySuccess and success options are set. If they are, I then override the beforeSend callback in order set the start variable to the current time. I then override the success function to grab the time after the ajax call has finish and subtract the difference from the original delaySuccess time. Finally, a timeout is set to the computed time which then calls the original success function.
I found this to be a nice way to achieve this effect and it can easily be used multiple times throughout a site.

Categories

Resources