Stopping Javascript timers - javascript

I'm trying to dynamically create PDFs on a webserver using PHP/wkhtmltopdf, which involves sending the PDF-generation process to the background in order to prevent the page timing out.
To check whether the job has completed successfully, I've used Javascript (which I suck at) and more specifically jQuery/AJAX to continuously query the server looking to see if wkhtmltopdf's process has ended. If its still running, the PHP script returns nothing and simply exits. If the process has ended successfully, a html link to the PDF is generated and then dumped into a <div></div>.
All the server side code works flawlessly however I'm stuck on the Javascript component. The code below kinda works but instead of the timer stopping after a PDF has been generated, it continues to query the server. How do I get it to stop?
$('#pdfmodal').on('shown', function () {
pdf(); // fire PDF generation process function
(function worker() {
$.ajax({
url: 'pdfpidcheck.php',
success: function(data) {
if(data == ''){
// Schedule the next request if nothing returned (i.e. still running)
setTimeout(worker, 5000);
} else {
// dump link to pdf
$('.pdfmodal').html(data);
}
}
});
})();
})

To stop a timer, you just remember the returned value from setTimeout() and call clearTimeout() on it.
var id = setTimeout(fn, 5000);
// then some time later
clearTimeout(id);
In the code you've shown us, this should not be an issue unless you are calling worker() from some other place than what you show us or unless the .on() handler gets called a second time while a PDF is being created. Your current code doesn't look like it knows how to handler two PDFs being created at the same time or a second even triggered while the first one is still processing.
You could protect against multiple timers running like this:
$('#pdfmodal').on('shown', function () {
var modal = $(this);
pdf(); // fire PDF generation process function
(function worker() {
$.ajax({
url: 'pdfpidcheck.php',
success: function(data) {
var timer = modal.data(timer);
if(data == ''){
// make sure we never have more than one timer running
if (timer) clearTimeout(timer);
// Schedule the next request if nothing returned
// (i.e. server process still running)
timer = setTimeout(worker, 5000);
// save timer for later use
modal.data("timer", timer);
} else {
// clean up timer data
if (timer) clearTimeout(timer);
modal.removeData("timer");
// dump link to pdf
$('.pdfmodal').html(data);
}
}
});
})();
})

Related

Wrong use of Javascript setInterval()

I have a function called using setInterval of JavaScript, which in some scenarios is called multiple times without the interval gap defined (I suspect this is because the intervals are not cleared properly and I'm creating multiple intervals, but I'm not sure).
I can not reproduce the problem locally.
The code uses Twirl but it's basically JS:
function refreshCheckInRequests() {
if (interval) { // If there is an interval running stop it.
clearInterval(interval);
}
jsRoutes.controllers.ExtranetSecuredController.findPendingCheckInRequests("#gymId").ajax({ // Ajax call using Play Framework
success: function (data) {
$("#checkin-request-container").html(data);
addRowListeners()
},
error: function (data) {
if (data.status == 401) {
errorSwitchGym("#Messages("extranet.switch.gym")");
//location.reload();
}
else {
unexpectedError(data)
}
},
complete: function() {
interval = initInterval(); // At the end of the call init the interval again
}
});
}
function initInterval() {
return setInterval(function () { refreshCheckInRequests(); },
20000);
}
var interval;
refreshCheckInRequests();
$("#checkin-request-refresh").click(function (event) {
refreshCheckInRequests();
event.preventDefault();
});
I could use setTimeout instead because at the end, I always call refreshCheckInRequests once, I stop the interval, and at the end I create a new one.
If I use timeout I have to call again my function at the end of the execution of the callback of timeout (like I'm doing right now). If something goes wrong, my callback will never be called again.
Anyway, I would like to know what's going on here. Am I missing something? Am I doing something wrong? Any suggestions?
You're clearing the current interval every time refreshCheckInRequests is called, but there is a delay between when refreshCheckInRequests is called and the new interval is assigned. Because refreshCheckInRequests also runs when an element is clicked, the following scenario could result in an unterminated interval:
User clicks, current interval is cleared, asynchronous findPendingCheckInRequests runs
User clicks again, no interval currently exists (nothing to clear), another asynchronous findPendingCheckInRequests runs
Response from first findPendingCheckInRequests comes back. complete handler runs, interval is assigned to the new interval
Response from second findPendingCheckInRequests comes back. complete handler runs, interval is assigned to the new interval over the old interval
The first created interval remains running, but there no longer exists a reference to it, so that first interval continues repeating forever.
So, try clearing the interval at the moment you reassign interval, ensuring that every new interval will always clear the old one, if an old one is running:
complete: function() {
clearInterval(interval);
interval = initInterval();
}

getJSON done callback

I have the function bellow called every 5 seconds to get data from the server, which is flask/python. My question is how can I adapt the getjson call to have callback when the data is successfully retrieved.
I know there's .done .fail and so on, but I was wondering if I can keep this structure and just add bellow it, but I don't know the syntax in this particular case, hope this isn't too confusing, thanks for reading, here's the code.
// get data from the server every getDataFromServerInterval milliseconds
var getDataFromServerInterval = 5000;
function getData(){
// request timesince table entries from server for user...
$.getJSON($SCRIPT_ROOT + '/_database', {
action: "getUserTable_timesince",
username: $('input[name="username"]').val()
}, function(data) { // do something with the response data
timesince_dataBuffer = data;
});
return false; // prevent get
}
// get data from the server every getDataFromServerInterval milliseconds
setInterval(getData, getDataFromServerInterval);
You could do something like this. Instead of processing the data in getData or using a callback, take advantage of the promise that $.getJSON returns. Have a separate function that is called by the timeout which calls for the data, then processes it. It neatly separates your code out into more managable functions.
var getDataFromServerInterval = 5000;
function getData() {
return $.getJSON($SCRIPT_ROOT + '/_database', {
action: "getUserTable_timesince",
username: $('input[name="username"]').val()
}
}
function wrangleData() {
getData().then(function (data) {
console.log(data);
});
}
setInterval(wrangleData, getDataFromServerInterval);
I found a partial solution, I realized that I can add a callback at the end of the function that handles the data received, which is somewhat equivalent to .done in a different getjson call structure, I'm not sure yet if the function gets called before or after the data is received.
// global timesince buffer, holds
var timesince_dataBuffer;
// get data from the server every getDataFromServerInterval milliseconds
var getDataFromServerInterval = 5000;
function getData(){
// request timesince table entries from server for user
$.getJSON($SCRIPT_ROOT + '/_database', {
action: "getUserTable_timesince",
username: $('input[name="username"]').val()
}, function(data) { // do something with the response data
timesince_dataBuffer = data;
updateEntryStruct(); // the hope is to call this when data is received
});
return false; // prevent get
}
// get data from the server every getDataFromServerInterval milliseconds
setInterval(getData, getDataFromServerInterval);
This is the solution I came up with.
var timesince_dataBuffer;
function getData(){
// gets user's entries from sql table
$.getJSON($SCRIPT_ROOT + '/_database', { // $SCRIPT_ROOT, root to the application
action: "getUserTable_timesince",
username: $('input[name="username"]').val()
}, function(data) { // if a response is sent, this function is called
timesince_dataBuffer = data;
updateEntryStruct(); // recreate the structure of each content, buttons etc
});
return false;
}
I get the data, put in a global variable, call another function which takes that data and re-creates a structure for each object received, this way I don't recreate parts of the structure which are static, most importantly the buttons.
Another function is called every 1 second, which updates the dynamic parts.
(formatted time) passed since
(event name)
Anyway, this is actually my final project in CS50, I started by communicating with the server via form submissions, refreshing the page each time the user pressed a button, then I did it by ajax, but I was sending requests to the server every 2 seconds, and having unresponsive buttons because I would keep re-creating the buttons themselves on a time interval.
And now the page feels responsive and efficient, it's been a great learning experience.
If anyone wants to check out the code, everything is here.
https://github.com/silvermirai/cs50-final-project
It's basically a bunch of random functionality that came to mind.
The application can be found here as of now.
http://ide502-silvermirai.cs50.io:8080/

JS - Delay the execution of a script until all data is defined/available

I have a script, that uses data from a data layer. If I fire the script too early it doesn't work because the data (I refer to in the script) is not defined yet. Therefore I delayed the script (1500ms). The problem is that the script should run as early as possible. Sometimes the data (from the data layer) is available faster, sometimes later...
so instead of delaying my script always 1500ms, I would like to run the script after 500ms and if it doesn't work because the data/property is not defined/available yet, I want to run it again after another 500 ms.... etc.
how could I action this?
thx!!
// set function to run every 500 ms, and store reference
var interval = setInterval(function(){
// if the data is available, clear the interval, and process data
if(typeof data !== 'undefined') {
clearInterval(interval)
console.log('data is ready');
// ready to start processing
}
}, 500);
However, this method is rarely the best approach. Depending on how your data becomes available, it would be better to use callbacks/promises or event listeners.
For example, using jQuery to fetch some data, then react in callback...
$.get('/some/data/uri', function(data){ /* do something with data */ });

Send information about clicked link to the server before redirect

We're creating a click tracking app, that builds heatmaps. I'm writing a script which users are suppose to insert into their pages for tracking to work.
It works fine on elements, which doesn't require a redirect or form submit. For example, if I click on h1 or p or whatever, it works perfectly correct. But, if I click on a a, request to our server never happens before the normal redirect.
In the last couple of days I tried a lot of ways to do that. First of, I tried a normal AJAX call, since it was a cross-domain request I had to use JSONP, but again, that AJAX call did not have time to execute before the redirect. Adding async: false would have solved the problem, but it doesn't work with JSONP requests. So I decided to add a flag variable which indicates that it is safe to move on with redirect and used an empty while loop to wait until it becomes try in the ajax callback. But the while loop was blocking the execution flow, so callback never got a chance to set that variable to true. Here is some simplified code:
$(document).on('click', function (e) {
//part of the code is omitted
$.ajax({
url: baseUrl,
data: data,
type: "get",
dataType: "jsonp",
crossDomain: true,
complete: function (xhr, status,) {
itsSafeToMoveOn = true;
}
});
while(!itsSafeToMoveOn){}
return true;
});
The next thing I tried is to use unload page event to wait until total ajax calls in progress would become zero (I had a counter implemented) and then to move on with redirect. It worked in Firefox and IE, but in WebKit there was this error:
Error: Too much time spent in unload handler
After that I realized that I don't care about the server response and using img.src for the request would be an ideal fit for this case. So at this point code looks like this:
$(document).click(function (e) {
//part of the code is ommited
(new Image).src = baseUrl + '?' + data;
if (tag === "a" || clickedElement.parents().has("a")) {
sleep(100);
}
return true;
});
That way I increased the overall script performance slightly, but problem with links remains unchanged. The sleep function appears to be also blocking the execution flow and request never happens.
The only idea left is to return false from the event handler and than redirect manually to the clicked element's href or to call submit() on the form, but it will complicate things to much and believe me it's already a huge pain in the ass to debug this script in different browsers.
Does anyone have any other ideas?
var globalStopper = true;
$(document).on('click', function (e) {
if (globalStopper === false)
return true; //proceed with click if stopper is NOT set
else {
globalStopper = false; //release the breaks
$.ajax({
//blahblah
complete: function (xhr, status,) {
$(elem).click(); //when ajax request done - "rerun" the click
}
});
return false; //DO NOT let browser process the click
}
});
Also, instead of adding image, try adding script. And then add the script to the HEAD section. This way the browser will "wait" until it's loaded.
$(document).on('click', function (e) {
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(scriptTag);
return true;
}
I would take a look at the navigator sendBeacon API mentioned in this stack overflow answer or directly linked to here.
From the description on the site
navigator.sendBeacon(url, data) - This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the document.
You can save information to ajax request in cookies or localStorage and make any worker that will send information. Saving to cookies or localStorage is faster then ajax-request. You can do next:
$(document).click(function (e) {
var queue = localStorage.getItem('requestQueue');
queue.push(data);
localStorage.setItem('requestQueue',queue);
});
$(function(){
setInterval(function(){
var queue = localStorage.getItem('requestQueue');
while (queue.length > 0) {
var data = queue.pop();
$.ajax({
...
success: function(){
localStorage.setItem('requestQueue', queue);
}
});
}
},intervalToSendData);
});
So, when user click on link or send a form, data will be saved to storage and after user go to next page, this worker starts and send data to your server.
The JavaScript is basically executed in single thread. It is not possible to have your callback function executed and at the same time have an infinite loop waiting for a flag variable from it. The infinite loop will occupy the single execution thread and the callback will never be called.
Best approach is to cancel the default handler of your event and bubbling for it (basically return false if you are really building your tracking code with jQuery), and do the necessary actions (redirect page to the necessary address if a link was clicked or trigger other default actions), but this would take a lot of careful work to recreate all the possible combinations of actiona and callbacks.
Another approach is to:
1) Look for something specific to your code in the event data
2) If it is not present - make an AJAX call and in its callback re-trigger the same even on the same element, but this time with your specific bit added to the even data; after the AJAX call return false
3) If your specific bits are present in the data - simply do nothing, allowing the default event processing to take place.
The either approach may bite, however.
So if I understand right, you want your ajax logs completed before the page unloads and follows a link href. This sounds like a perfect case where you could consider using Deferreds in jQuery.
When your user clicks on anything that's supposed to take him away from the page, just check your promise status. If it's not resolved, you could throw a modal window over the page, and ask the user to wait til the progress is complete. Then, add a new pipe to your deferred, telling it to change the location href once everything is complete.
Let me know if this is the scenario. If it is, I'll explain in more detail. No use continuing if I didn't understand your requirement properly

Wait For Timer To Finish Before Function Runs

I think this fairly basic but I can't seem to find one on-line. This can be in JavaScript or jquery.
I need to create a timer for about a 1-2 seconds.
Meanwhile another function is using ajax to pass data to a server side php file. When the response text gets back it displays it on the page.
At the moment I have the ajax function running and the time taken for the function to complete is about 0.1 seconds. But this makes the page look really jumpy as the content changes css styles while the ajax is waiting for a response and then back to the original on return (hope that makes sense).
Anyway to combat this I would like the function to check if the timer has ended before displaying the response text.
The only way I can get it at the moment is by creating a interval timer for a second and running the ajax function when that completes, but this is not ideal as the viewer MUST wait the extra second or 2 even if the request to the server takes over that time to complete.
Hope All Of That Makes Sense & Thanks Very Much For Your Time.
Chris
You're better off attaching your function as a "success" handler to your AJAX call rather than using a fixed timer. How you attach it depends on which library, if any:
jQuery 1.4 style (still works in 1.5)
$.ajax({
// your AJAX options
success: yourFunc
});
jQuery 1.5 style
$.ajax({
// your AJAX options
}).done(yourFunc);
DOM style
// after creating your XHR
xhr.onreadystatechange = function() {
if (this.readyState === 4) { // 4 means the request has completed
if (this.status !== 200) { // 200 is success, so anything else...
// log or report error
return;
}
// call your other function, which uses the AJAX data
yourFunc(this.responseText);
}
};
I would use a setTimeout or the jQuery .delay() if a timer is your only option.
$.ajax({
success: function() {
setTimout(function() {
// styling code goes here
}, 1000);
}
});

Categories