I have been working on a pretty big javascript function, and when I finally thought it was done, it stopped working completely.
Here it is:
function getLinksFrom(title) {
var returnArray = [],
plcontinue = '',
url = 'http://en.wikipedia.org/w/api.php?action=query&prop=links&titles=' + title + '&format=json&pllimit=500&plnamespace=0&callback=?';
while (returnArray.length === 0 || plcontinue !== '') {
if (plcontinue !== '') {
url = 'http://en.wikipedia.org/w/api.php?action=query&prop=links&titles=' + title + '&format=json&pllimit=500&plnamespace=0&plcontinue=' + plcontinue + '&callback=?';
}
$.ajax({url: url,
dataType: 'json',
success: function(data) {
console.log(data);
for (key in data['query']['pages']) {
links = data['query']['pages'][key]['links'];
}
for (var i = 0; i < links.length; i += 1) {
returnArray.push(links[i]['title']);
}
if (data.hasOwnProperty('query-continue')) {
plcontinue = data['query-continue']['links']['plcontinue'];
} else {
plcontinue = '';
}
}
});
}
return returnArray;
}
from what I can tell, it must be getting stuck in the while loop, as the page just freezes up. But with each loop, returnArray grows, and I'm testing it with something that won't set plcontinue to anything.
Any idea what is going wrong? Possibly something with asynchronous loading, I'm new at that.
EDIT: So I've figured out via helpful comments that it's looping again and again making more and more requests without waiting for the ajax to finish before starting again. How can I stop it from doing that?
The key is that AJAX is asynchronous. That means that when JS needs to perform request, it sends it and goes on, continuing to execute the rest of the code. When request is done, callback is fired.
So in your code it would works like that:
Initialization of variables
While loop condition check. Condition is true, so go to the loop body.
Executing ajax request. We send request and go on.
Again check condition. Since nothing changed (request haven't any time to complete), condition is still true.
And so on.
Eventually, maybe, one of dozen of request will end, modify returnArray and plcontinue, but this could take long time because what you have in your function body is, basically, infinite (almost) loop, which takes most of CPU resources.
The solution is to replace iteration with recursion: instead of iterating in infinite loop untill success, you can recurse until success. Delete while loop from getLinksFrom and add getLinksFrom() to the ajax request callback.
Another approack is to do that request synchronous. There is a special flag for it.
Related
I have an array of links with a url inside them, so I need to call 'get', wait for the response and extract what I want out of it and set an image's src to what I get back
I'm facing a problem where by the time the first 'get' is ready, the loop has finished, so basically every iteration of the loop gets the LAST 'data' response. Basically, only the last iteration of i gets a value, and even that's not always the right answer. It's just the last answer to come back
How do I go about fixing this issue?
for(i of instructors) {
jQuery( document ).ready($.get( i["image-link-post"], function( data ) {
i['image-link'] = data.guid.rendered;
}));
}
Since the loop is dealing with async calls, the iteration do not wait for ajax call to complete, and hence by the time the ajax call received a response, i is pointing to some other item of iteration. In this case, we need to maintain a local variable of iterating item and then pass the local reference to the ajax response handler. Checkout with below refactored code:
// Changed var image to let image
for (i of instructors) {
let image = i;
jQuery(document).ready($.get(image["image-link-post"], function (data) {
image['image-link'] = data.guid.rendered;
}));
}
// Below code also works
for (i of instructors) {
(function (image) {
jQuery(document).ready($.get(image["image-link-post"], function (data) {
image['image-link'] = data.guid.rendered;
}));
}(i));
}
I've got a tool in place which is splitting a large query into manageable chunks, then using a simple AJAX method to spit this out. The destination for the AJAX form is just a script which delegates some form data to a function, including which 'chunk' to process.
<script>
var passes = Math.ceil($max / $offset);
for (i = 0; i < passes; i++)
{
$.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form" . $i . "').serialize(),
success: function(data){
$('#update" . $i . "').append(data);
}
});
}
</script>
As this can iterate a few times, I was looking to execute a script for when the looping (i.e. the function itself) has finished.
As this isn't anything too snazzy, I thought it would be a simple case of adding if(i == passes -1) { alert('test');}if(i == passes -1) { alert('test');} to the end of the loop, like this:
for (i = 0; i < passes; i++) {
$.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form" . $i . "').serialize(),
success: function(data){
$('#update" . $i . "').append(data);
}
});
if(i == passes -1) { alert('test');}
}
....but it loads this as soon as the page loads, before the loop.
Likewise, adding a simple function after the loop acheives the same result, too.
I would have thought (but I'm quite fresh at JS) that it would complete a loop before attempting to execute the second instance of 'i', but it doesn't seem to do so - the page acts like all of the requests are sent instantly, completing the loop, executing the code, then allowing the functions within 'success' to echo back in their own time. This seems even more evident in that sometimes it will append the results for the second iteration of i before the first.
Questions...
1) Have I made an error in how I've constructed the loop, considering the purpose?
2) Why does the loop seem to execute code after the loop when it seems like it is still processing the loop itself?
What I'm trying to achieve
Each loop should perform a MySQL query, return the function's HTML output, then print it before moving on to the next. It does do this 99% correct, just with the occasional problem of it not always appending in order.
After all loops have completed and appended to the container, I would like to run some code to confirm that the operation is complete.
Many Thanks in advance, hope this is clear enough
This is a "Promise" based solution to your problem.
First, decompose each pass into a function that does one unit of work:
function makePass(i) {
return $.ajax({
type: 'POST', url: 'do.php?p=' + i, data: $('#form' + i).serialize()
}).then(function(data) {
$('#update' + i).append(data);
});
}
Then you can make a general purpose function that pseudo-recursively makes the desired number of calls to the supplied function, calling it each time with the current pass number, and finally returning a new "resolved" promise once every pass has been completed:
function makeNPasses(f, n) {
var i = 0;
return (function loop() {
if (i < n) {
return f(i++).then(loop);
} else {
return Promise.resolve();
}
})();
}
You can then register a handler to be invoked once everything is done:
var passes = Math.ceil($max / $offset);
makeNPasses(makePass, passes).then(function() {
console.log("All done!");
});
Javascript has an async flow.. It doesn't wait for the above request to get the data from somewhere... Instead it just keeps firing statements in a row.
To escape this, there are 3 options.
The ideal approach is to make 1 single http request to server and get all data in form of json array. This will be more efficient, easy and time-saving and also follows the best practices.
Make an async call of ajax. You will get the good information about it in this answer jQuery ajax success anonymous function scope. But again.. Callbacks are recommended and not doing async false. Instead .when() or .then() is easier.. because ultimately they too are callbacks.
Recursive Function can help you through such kind of tasks. It is a dirty approach because ES5 Doesn't let you iterate much deeper using recursive functions. But ES6 does allow it to be 10000 iterations. But its not a good approach. It increases overhead and bottleneck your page load.
AJAX calls are asynchronous and therefore they cannot succeed before the loop ends (as JavaScript is non blocking). In fact what you are wanting to do is to perform action after AJAX calls, not just after the loop (which is synchronous) so I would suggest either using Promise or chaining or aggregating success events like below:
var passes = Math.ceil($max / $offset);
for (i = 0; i < passes; i++) {
$.ajax({
type: 'POST',
url: 'do.php?p=' + i,
data: $('#form' + i).serialize(),
success: function(data){
$('#update' + i).append(data);
resolve();
},
error: reject
});
}
var resolved = 0;
function resolve() {
if(++resolved >= passes) {
alert('test');
}
}
function reject() {
alert('One of AJAX requests failed');
}
I have this inside an ajax function
$.each(data['Result'][0], function(Key, Value) {
InputGen(Value['Type'], Value['Name'], Value['Other'], Value['Value'], function(Html){
Table = Table + "<tr><td>"+Key+"</td><td>" + Html + "</td></tr>";
});
});
and InputGen has a callback from another ajax function but when i run this the loop does not seem to be waiting for the ajax to finish. how would i achieve this?
...the loop does not seem to be waiting for the ajax to finish.
No, because the "a" in "ajax" stands for asynchronous; it doesn't happen when you make the call, it happens later. But there's no reason the $.each loop would know to sit around and wait for it to finish.
If you don't need it to (e.g., it's okay for the ajax calls to overlap, which normally it should be unless they rely on each other), look at Rocket Hazmat's approach.
If you need each ajax call to wait for the previous one to finish, you can't use $.each (or at least, not the way you were); instead, use an index and respond to the callback by triggering the next request:
// Start with first entry
var index = 0;
var array = data['Result'][0];
// Do the first request
doRequest();
function doRequest() {
var value = array[index];
InputGen(value['Type'], value['Name'], value['Other'], value['Value'], function(Html) {
Table = Table + "<tr><td>"+index+"</td><td>" + Html + "</td></tr>";
// This request is done, move to the next if any
if (++index < array.length) {
doRequest();
}
});
}
Side note: Overwhelmingly in JavaScript, variables and non-constructor functions are named starting with a lower-case letter: value rather than Value, etc. So I used index, array, and value above.
Side note 2: value['Type] can be more simply written as value.Type. (And so on.)
This is because AJAX is asynchronous. Nothing is going to wait for it to finish. The callback will run in the future at some point when the call is done. By then your $.each (and the code after) is long done.
The solution here is to use promises. That way you can run a callback once all the AJAX calls are done.
You can use jQuery's $.Deferred for this. Without editing the InputGen() function, you can do something like this:
var promises = [];
$.each(data['Result'][0], function(Key, Value) {
var d = new $.Deferred;
InputGen(Value['Type'], Value['Name'], Value['Other'], Value['Value'], function(Html){
d.resolve([Key, Html]);
});
promises.push(d.promise());
});
$.when.apply($, promises).done(function(){
for(var i=0, length=arguments.length; i < length; i++){
var ele = arguments[i],
Key = ele[0],
Html = ele[1];
Table = Table + "<tr><td>"+Key+"</td><td>" + Html + "</td></tr>";
}
// In here is where you can use your updated `Table` variable.
// You *cannot* use it outside of here, since it was not updated yet
});
I am trying to send a xmlHttpRequest in a while loop and want to do something with the response in the same while loop. Since the requests are asynchronous, how can I achieve it? I need to execute everything serially
while(i < n){
response = self.sendHttpRequest(params);
//do something with the response
}
Any help would be appreciated.
Even If I use a callback, how can I get back to same while loop after executing the callback?
There are two ways I can think of:
1. Add a polling loop after the get call that waits until the response.readyState is set and then process the response:
while(i < n){
response = self.sendHttpRequest(params);
while( response.readyState != 4 ){
// polling wait
}
//do something with the response
}
This option is not really recommended since it stops the flow of the code and you can get stop in the loop if the readyState never changes (not likely, but possible with errors).
2. You can encapsulate the request in a function that will be called recursively when the last response handling finishes:
var i = 0;
function handle( response ){
//handle response
i++;
if( i < n ) sendRequest();
}
function sendRequest(){
// Your request setup code
response.onreadystatechange = handle;
response = self.sendHttpRequest(params);
}
The second method is preferred in my opinion, as it maintains the asynchronicity of the html request call, and doesn't stop the flow of the code, however it does "break" the loop structure. The first method keeps the loop structure, but is not very good coding practice.
Are you using any ajax library or plain js. If you are not using any library ,you can pass third argument to open method false.like below
var xmlHttp=new xmlHttpRequest();
xmlHttp.open({YOUR_METHOD},{YOUR_PATH},false);
Passing false to open method makes synchronous call .so you can handle the return in same loop.
I have an ajax call that retrieves data and on the success of it, runs a loop and runs functions that run more ajax calls.
CODE:
success: function(data){
// FIRST MAKE SURE DATA WAS FOUND
console.log(data);
if(data["status"] == "found")
{
// CREATE NEEDED ARRAY FROM SINGLE STRING
var restrictionArray = data["data_retrieved"].split(',');
loop_amount = restrictionArray.length; //<!-- AMOUNT TO BE LOOPED FOR BUILDING FORMS
//var Action = this.Elevation.getActionsByOptionId(Option.getID())[i];
for(var j = 0; j < loop_amount; j++)
{
var EditRowRestriction = OptionRules.prototype.getEditRowRestriction(j);
var check = $(EditRowRestriction).find("select");
console.log(check[0]);
var EditRowRestirction_select_ability = OptionRules.prototype.getEditRowRestriction_select_ability(j);
//var EditRowRestirction_access_ability = OptionRules.prototype.getEditRowRestriction_access_ability(j);
EditRowRestriction.onremove = function()
{
$(this).next().empty(); <!-- RESTRICTION SELECT ABILITY REMOVE
//$(this).next().next().empty(); <!-- RESTRICTION ACCESS ABILITY REMOVE
$(this).empty();
//var Action = this.Action;
//that.removeAction(Action);
}
tbody.appendChild(EditRowRestriction);
tbody.appendChild(EditRowRestirction_select_ability);
console.log(check[1]);
}
}
},
error:function(){
alert("An error occured, please try again.");
}
Heres the problem, inside the for loop; those methods link to another method that invokes an ajax call. What happens here is even before the ajax call's finish, the loop is always continuing and those methods are always being called. What I want to do is to stop the loop until those methods have returned based on the ajax call's being finished. And than to invoke the last 2 lines of code within the loop:
tbody.appendChild(EditRowRestriction);
tbody.appendChild(EditRowRestirction_select_ability);
What would be my best approach to accomplishing this?
Suggestions, thoughts?
It would be best to consolidate all of this looping with a single server-side script, however, if that isn't an option, you can use .then:
var def = $.Deferred(function(def){
def.resolve();
}).promise(); // used to start .then chain
for (var i = 0; i < 10; i++) {
def = def.then(function () {
return $.ajax({});
});
}
def.done(function(){
// All requests from chain are done
console.log('all done');
});
http://jsfiddle.net/kg45U/
You could modify the calls to $.ajax within the loop to execute synchronously, rather than asynchronously. (See SO question How can I get jQuery to perform a synchronous, rather than asynchronous, AJAX request?.) This would have the effect of "pausing" the loop while the inner ajax calls execute. This would be the most straightforward approach, but does have the disadvantage of locking up the user's browser while those ajax requests execute.
The alternative is to break up the code with the loops into pieces, so that you complete the processing with the loop in a callback function that is invoked after the inner-ajax calls have completed.