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();
}
}
Related
Can someone help me rectify the issue related to the setInterval? I'm fairly new to JavaScript, I'm not sure what's wrong here. I have this block in my page:
GlobalTicker.prototype.TickElements = function () {
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
};
GlobalTicker.prototype.initializeElement = function () {
for (var i = 0; i < this.tickerElements.length; i++) {
var existingRun = this.tickerElements[i].secs;
var elementId = $('#' + this.tickerElements[i].id + ' .comment-editor').find('.ticker');
existingRun -= 1;
$(elementId).text(existingRun);
if (existingRun === 0) {
$(elementId).remove();
this.tickerElements.splice(i, 1);
if (this.tickerElements.length == 0) clearInterval(this.tickerElements.timer);
}
}
};
Then somewhere in the code, I have this call in a function
var objTicker = new GlobalTicker();
CommentManagement.prototype.createComment = function (domObject) {
objTicker.TickElements();
};
This function call actually invokes the setInterval function and runs the first iteration and jumps to the initialiseComment(); but once this block is executed, on the next interval, instead of executing the initialiseComment(); again, it jumps back to my function call CreateComment();. What am I doing wrong here?
setInterval() requires a function reference. You were calling the function itself and passing the return result from executing the function (which was undefined) to setInterval(). Since that return value is not a function, there was no callback function for setInterval() to call. Thus, your method was executed once when you first called it, but couldn't be called by the interval.
To fix it, you should change from this:
this.timer = setInterval(this.initializeElement.apply(this) , 1000);
to this:
var self = this;
this.timer = setInterval(function() {self.initializeElement()}, 1000);
Note, the value of this will also be different in the setInterval() callback than the value you want so the one you want is saved here in self so it can be referenced from that. There's also no need to use .apply() in this case because calling a method on an object will automatically set the this pointer as needed.
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)
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 want my handler of the ready event will fire after all other handlers are done.
It's extremely handy for manipulating plugins' undesired actions.
If I write my handler after all others, it only guarantees it will fire after all others fired, not finished:
$(function() {
setTimeout(function() { alert('other handler'); }, 500);
});
$(function() { alert('my handler'); });
Fiddle
In that code, my handler alerted first.
I read that before jQuery version 1.4 the readyList was public. so in version 1.7 I have no idea how I can tell that my handler is the last handler or not.
If the idea is that you don't control the other ready handlers, then given your example where another handler used a setTimeout, you can never actually know (without inspecting the other code) if your code will run after all other code.
The readyList wouldn't help even if it was public, because in your example, the handler with the setTimeout will be removed from the readyList long before the setTimeout handler runs. The readyList Array doesn't have any control over that sort of asynchronous code either.
So if you don't control (can't modify) the other code, then I really don't have a solution. But if the other code is just long running, but not asynchronous, then there wouldn't be any issue, because if your code is the last .ready() handler assigned, it shouldn't matter how long the other handlers take to execute. If their code is synchronous, it will force yours to wait until they're complete. It's just that if they're using asynchronous code, like your setTimeout example, then there's nothing you can do short of examining the other code, and modifying yours to make sure it fires last.
You can use something like this:
function Join(cb) {
var paths = 0;
var triggerCallback = cb;
this.add = function () {
paths ++;
return this.call;
};
this.call = function () {
paths --;
if (paths == 0)
if (triggerCallback)
triggerCallback();
};
return this;
}
An example:
function finishedAll() {
alert("All finished");
}
window.join = new Join(finishedAll);
function sampleCall(callJoinHandle) {
alert("Not done yet.");
if (callJoinHandle) callJoinHandle();
}
var cb1 = join.add();
setTimeout(function () { sampleCall(cb1); }, 1000);
var cb2 = join.add();
setTimeout(function () { sampleCall(cb2); }, 1000);
var cb3 = join.add();
setTimeout(function () { sampleCall(cb3); }, 1000);
An idea could be creating an array of deferred to use inside every ready function (except the last one), resolving each one when the snippet has completed.
Then, in the last ready function you could simply check the promise resolution with $.when and then execute some other code: e.g.
var dfdArray = [];
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('another simple handler');
dfd.resolve();
}, 2000);
});
$(function() {
var dfd = $.Deferred();
dfdArray.push(dfd);
setTimeout(function() {
console.log('first handler');
dfd.resolve();
}, 1200);
});
$(function() {
$.when.apply($, dfdArray).done(function() {
alert('my final handler');
})
});
See fiddle in action here: http://jsfiddle.net/DXaw5/
I don't know if it is possible for you to create a queue for all the functions like
var queue = [];
queue .push(fun1);
queue .push(fun2);
//execute the first function and remove it.
(queue .shift())();
I usually use the following pattern, simply keepig a counter of finished async functions:
var fired = 10;
var finished = 0;
for (var i = 0; i < fired; i++) {
// Call an asynchronous function 10 times
async_function(function() {
// When asynchronous function finishes,
// we check if it was the last one.
if (++finished == fired) all_ready();
});
}
The same in coffeescript:
fired = 10
finished = 0
(async_function -> all_ready() if ++finished == ready) for n in [0...fired]
(We call the same function for 10 times to keep the example simple, while in reality you may of course call different functions, but the same idea apply; in callback function you check the counter.)
I asked a question yesterday, I accepted the answer, but sometime later, I came to know that the solution was not complete. The question is :-
Insert a JQuery click handler that executes before the ones already registered
using setTimeout(handler,0); returns immediately and I can not use return setTimeout(handler,0);. How can I run this handler synchronously and not allow the parent function to complete until this handler is completely executed and I get the return value out of it ?
I am in hurry, so I am asking it again, rather than editing that question again.
You don't need to use setTimeout. If u don't use setTimeout, your handler reamins synchronous, and you can return all the value u want in your function.
<script>
function f2() {
alert('Handler declared in HTML')
}
buttons = document.getElementsByTagName('input'); // refine this, later
for (i = 0, max = buttons.length; i < max; i++) {
oldonclick = buttons[i].onclick;
buttons[i].onclick = function() {
alert('Prepend handler');
oldonclick();
}
}
</script>
Since timeouts are asynchronous you’ll need to set the variable from within the timeout (and/or call a callback function).
var x = 1;
setTimeout(function() {
x = 2;
}, 2000);
Here’s an example with a callback function. You need this is you want to do something with the variable as soon as it’s changed.
var x = 1;
function callback(x) {
console.log(x);
}
setTimeout(function() {
x = 2;
callback(x);
}, 2000);
This will log 2 as soon as the timeout is executed.
Depending on what exactly it is you’re trying to do, you may not need timeouts at all, which avoids asynchronicity and a lot of trouble.
Quick answar: What about changing:
setTimeout(clickhandler, 0);
to
eval(clickhandler)();
to
eval(clickhandler);
$(document).ready(function() {
$("input[type=button]").each(function() {
// your button
var btn = $(this);
// original click handler
var clickhandler = btn.data("events").click[0];
btn.unbind("click", clickhandler);
// new click handler
btn.click(function() {
alert('Prepended Handler');
clickhandler();
});
});
});
function f2() {
alert('Handler declared in HTML');
}
And now clickhandler is a function, right?
See: jQuery: Unbind event handlers to bind them again later