I am a complete noob to Ajax so please forgive me if this is a completely asinine piece of code:
for (var i=0; i<11; i++) {
jQuery('#position').html(i);
var offset = jQuery('#offset').html();
var postcall = 'controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset;
jQuery.post(postcall,function(data){
jQuery('#offset').html(data);
});
}
The goal here is to execute controller.php with the given values and plug 'offset' back into each call using the returned info. It works but it runs from 0 to 10 instantly and my webserver rejects the subsequent calls.
My goal is to make sure it doesn't call the php again until the last operation has completed.
The key is to make your next AJAX call inside of your callback function. That way, your next post will not occur until the first finishes. In your code, because .post() is non-blocking (asynchronous), it continues the loop immediately, incrementing i/#position and firing off the next .post().
To solve this, encapsulate your .post() in a wrapper function. Have a counter that tracks how many times it has been called. Call the function from the callback of the .post(), and you end up with a recursive function that will do the calls in sequence:
var position=0;
function doNextAJAXPost() {
if(position < 11) {
jQuery('#position').html(position);
position++;
var offset = jQuery('#offset').html();
jQuery.post('controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset, function(data){
jQuery('#offset').html(data);
doNextAJAXPost();
});
}
}
doNextAJAXPost();
use a self executing recursive function
(function callself(i) {
jQuery('#position').html(i);
var offset = jQuery('#offset').html();
var postcall = 'controller.php?url='+encodeURIComponent(scrapurl)+'&scrape_absolute='+absoluteep+'&scrape_season='+season+'&scrape_show='+showslug+'&scrape_defimg='+encodeURIComponent(defaultimg)+'&offset='+offset;
jQuery.post(postcall,function(data){
jQuery('#offset').html(data);
i++;
if ( i < 11 ) callself(i);
});
})(0)
Related
I want to make 2 api requests, but calling the second request inside the first's callback function doesn't seem right to me. Is there any way I can just call them both simultaneously and run the callback function only when I got respond from both?
You can use $.when() jquery function.
http://api.jquery.com/jquery.when/
You should use a variable outside the calls that increments on success. In the callback you test if it equals the number of calls, if true then you can call your finish method
Something like this:
var nbSuccess = 0;
var nbCalls = 2;
call_1.success
- nbSuccess++
- if nbSuccess == nbCalls {doFinish}
call_2.success
- nbSuccess++
- if nbSuccess == nbCalls {doFinish}
function doFinish...
Calling the second request inside the first one callback make them Synchronous. The second will only be called when the first has finished.
If you want two send the two call and handle only when the two are done you may (not tested)
Use the same callback for both and use a boolean to ensure both are done.
Something like (pseudo code):
var a1=false; a2=false;
a1.ajax(callback(){a1=true; doAction()}
a2.ajax(callback(){a2=true; doAction()}
function doAction() {
if (a1 && a2) {
...
}
You can do this
var response = 0;
var callback = function () {
if (response === 2){/* code */}
};
// first request
$.get(url).done(function () {
response++;
callback();
});
// second request
$.get(url).done(function () {
response++;
callback();
});
I hope this might helps you. Try this.
$.ajax("yourUrl",function(){
}).done(function(){
$.ajax("yourUrl",function(){
}).done(function(){
// Do your task
});
});
I have some javascript code which executes when a link is clicked.
after that code has executed I want to display an alert, however the alert is being shown before the earlier code completes
document.querySelector('.btn-primary').addEventListener('click', function(evt) {
var loops = 10;
var chunkLength = Math.ceil(file.size / loops);
var start = 0;
var stop = chunkLength;
for (var i = 0; i < loops; i++) {
var blob = file.slice(start, stop);
readText(blob);
start = stop;
stop += chunkLength;
}
alert('entire file loaded');
print();
}, false);
updates
I know because the readText method updates a progress bar and this happens after the alert pops up, its not an ajax call just a local method (which is asynchronous...)
eventually, I'm looking to replace the alert with a call to save a file but its no use if the file content hasn't been generated yet as I just save an empty file.
In that case, you'll need to extend readText to accept a callback function:
function readText(blob, callback) {
var reader = new FileReader();
reader.onload = callback;
// whatever else your function does to load the file
}
Then you invoke it like this:
readText(blob, function() {
alert('entire file loaded');
});
Of course if readText itself delegates its asynchronous work somewhere else, you'll have to pass the callback there too.
If the actual asynchronous function doesn't accept a callback parameter, you'll want to check for another way that it signals its caller of completeness. It may fire events. But that all really depends on how readText is implemented.
You mentioned that readText is an asynchronous method. This is why the alert shows before readText has finished executing. The for loop will run through all its iterations by which time calls to readText have not finished. The next statement to execute after the for, is the alert which is what you're seeing.
If readText provides a callback function, you will have to use that:
var counter = 0;
for (var i = 0; i < loops; i++) {
var blob = file.slice(start, stop);
readText(blob, function() {
counter++;
if(counter === loops) {
alert("entire file loaded");
}
});
start = stop;
stop += chunkLength;
}
This doesn't guarantee order of operations however. It is entirely possible that a call on a later iteration finishes before a call from an earlier iteration.
If readText doesn't have a callback argument and you can modify the source of that function, you will have to do something like this:
function readText(blob, callback) {
...
if(typeof callback === "function") {
callback();
}
}
I have a JavaScript function like the following.
function changeTheDom(var1, var2, var3) {
// Use DWR to get some server information
// In the DWR callback, add a element to DOM
}
This function is called in a couple of places in the page. Sometimes, in a loop. It's important that the elements be added to the DOM in the order that the changeTheDom function is called.
I originally tried adding DWREngine.setAsync(false); to the beginning of my function and DWREngine.setAsync(true); to the end of my function. While this worked, it was causing utter craziness on the rest of the page.
So I am wondering if there is a way to lock the changeTheDom function. I found this post but I couldn't really follow the else loop or how the lockingFunction was intended to be called.
Any help understanding that post or just making a locking procedure would be appreciated.
Don't try to lock anything. The cleanest way is always to adapt to the asynchronous nature of your code. So if you have an asynchronous function, use a callback. In your particular case I would suggest that you split your function up in one part that is executed before the asych call and one part that is executed afterwards:
function changeTheDomBefore(var1, var2, var3) {
//some code
//...
asyncFunction(function(result){
//this will be executed when the asynchronous function is done
changeTheDomAfter(var1, var2, var2, result);
});
}
function changeTheDomAfter(var1, var2, var3, asynchResult) {
//more code
//...
}
asyncFunction is the asynchronous function which, in this example, takes one argument - the callback function, which then calls your second changeTheDom function.
I think I finally got what you mean and I decided to create another answer, which is hopefully more helpful.
To preserve order when dealing with multiple asynchronous function calls, you could write a simple Queue class:
function Queue(){
var queue = [];
this.add = function(func, data) {
queue.push({func:func,data:data});
if (queue.length === 1) {
go();
}
};
function go() {
if (queue.length > 0) {
var func = queue[0].func,
data = queue[0].data;
//example of an async call with callback
async(function() {
func.apply(this, arguments);
queue.shift();
go();
});
}
}
};
var queue = new Queue();
function doit(data){
queue.add(function(result){
console.log(result);
}, data);
}
for (var i = 0; i < 10; i++) {
doit({
json: JSON.stringify({
index: i
}),
delay: 1 - i / 10.0
});
}
FIDDLE
So everytime you invoke your async function, you call queue.add() which adds your function in the queue and ensures that it will only execute when everything else in the queue is finished.
function animateGraph() {
var graph;
for(i=0; i<10; i++)
{
var start = new Date();
while((new Date()) - start <= 500) {/*wait*/}
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
}
}
The loop works. The wait works. But the document.getElement is not showing up until the last item in the array...why?
Using setTimeout will allow the code to run and not lock up the page. This will allow it to run the code and will not effect other elements on the page.
var cnt = 0;
(function animateGraph() {
document.getElementById("timeMark").innerHTML = phoneX[cnt].epoch;
cnt++;
if (cnt<10){
window.setTimeout(animateGraph,500);
}
})();
The while loop, waiting for a datetime, is not a good way to wait - it just blocks execution. It keeps the browser (including UI, and its updating) frozen until the script finishes. After that, the window is repainted according to the DOM.
Use window.setTimeout() instead:
function animateGraph(phoneX) {
var el = document.getElementById("timeMark")
var i = 0;
(function nextStep() {
if (i < phoneX.length )
el.innerHTML = phoneX[i].epoch;
i++;
if (i < phoneX.length )
window.setTimeout(nextStep, 500);
})();
}
Please note that this runs asynchronous, i.e. the function animateGraph will return before all phoneXes are shown.
Use setTimeout instead of a while loop.
https://developer.mozilla.org/en/DOM/window.setTimeout
Also try something like this.
Javascript setTimeout function
The following snippet uses a helper function to create the timers. This helper function accepts a loop counter argument i and calls itself at the end of the timer handler for the next iteration.
function animateGraph() {
var graph;
setTimeMarkDelayed(0);
function setTimeMarkDelayed(i) {
setTimeout(function() {
document.getElementById("timeMark").innerHTML = phoneX[i].epoch;
if (i < 10) {
setTimeMarkDelayed(++i);
}
}, 3000);
}
}
You actually need some sort of helper function, otherwise you'll end up overwriting the value of i in your for loop in every iteration and by the time your timers run out, i will already be 9 and all handlers will act on the last element in phoneX. By passing i as an argument to the helper function, the value is stored in the local scope of that function and won't get overwritten.
Or you could use setInterval like Radu suggested, both approaches will work.
I'm using the Asynchronous each() function plugin code posted at:
jQuery Tips and Tricks
It works well but I can't seem to exit a running callback function using return; or return true;. the consquence is that when the condition to "exit" is met, it stops all remaining $.forEach "loop" execution.
Since the plugin implements a setTimeout loop approach, perhaps I just need to know how to exit an already running setTimeout callback function? BTW - I'm avoiding using for loops or $.each() to avoid browser lockup while big json processing occurs. "backgrounding" the loop significantly improves performance of the UI.
$.forEach(json, 1000, function(idx,item) {
if(some_condition) return true; //exit this timeout iteration
//otherwise do something
});
jQuery.forEach = function (in_array, in_pause_ms, in_callback)
{
if (!in_array.length) return; // make sure array was sent
var i = 0; // starting index
bgEach(); // call the function
function bgEach()
{
if (in_callback.call(in_array[i], i, in_array[i]) !== false)
{
i++; // move to next item
if (i < in_array.length) setTimeout(bgEach, in_pause_ms);
}
}
return in_array; // returns array
};
jQuery.fn.forEach = function (in_callback, in_optional_pause_ms)
{
if (!in_optional_pause_ms) in_optional_pause_ms = 10; // default
return jQuery.forEach(this, in_optional_pause_ms, in_callback); // run it
};
Thanks much!
From the docs:
We can break the $.each() loop at a particular iteration by making the
callback function return false.
EDIT: This also applies to your custom forEach.