Disclaimer: I'm totally 'new' to javascript, so please excuse the absolute amateur code.
I have a progress bar script that polls a URL which dumps some json data. The script then updates a few html values based on the contents of said json, including a progress bar.
Here's a simplified version of the code:
function update_progress(status_url, index) {
bar_id = document.getElementById(String('bar' + index));
stage_id = document.getElementById(String('stage' + index));
$.getJSON(status_url, function(data) {
percent = parseInt(data['current'] * 100 / data['total']);
// change progress bar and stage_id values
bar_id.innerHTML = (percent + '%');
bar_id.style.width = (percent + '%');
stage_id.title = (data['status']);
stage_id.value = (data['status']);
stage_id.innerHTML = (data['status']);
// if state is unexpected then end poll
if (data['state'] != 'PENDING' && data['state'] != 'PROGRESS' && data['state'] != "COMPLETE") {
stage_id.innerHTML = (data['state']);
// otherwise keep polling every 1.5 seconds
} else {
setTimeout(function() {
update_progress(status_url, index);
}, 1500);
}
}
Sometimes the status_url can return a 500 error if the json data hasn't yet been compiled. So I have a button that restarts this poll by calling update_progress again.
The problem:
If a user clicks the 'restart poll' button, there will be two active polls for the same status_url.
There can be many polls ongoing at once to a bunch of different status_url's - the more there are, the slower the response.
So I'd like to avoid being able to poll something that is already ongoing
Question: is there a way to check if a process with the same values is already ongoing in JS?
I'm aware that this is inefficient, and I'll be moving away from JS completely for this polling system at some point - however I need to do some quick efficiency patching on this current release before I get to that.
Thanks in advance
Cancelling a javaScript interval
You can assign setTimeout to a variable:
var timer = setTimeout(...updateProgress...);
And then, when the user clicks the button to refresh the poll, you can do:
clearTimeout(timer)
To delete the original timeout process, then create another one.
This answer your question I think. However:
There is no need to force the user to re-try the polling manually.
If the getJson call fails due to a 500 error, instead of clearing and launching the polling again, you can just keep polling. Now, I'm not used to getJSON syntax, but from what I read in this answer, you would do something like the following:
function update_progress(status_url, index) {
bar_id = document.getElementById(String('bar' + index));
stage_id = document.getElementById(String('stage' + index));
$.getJSON(status_url, function(data) {
percent = parseInt(data['current'] * 100 / data['total']);
// change progress bar and stage_id values
bar_id.innerHTML = (percent + '%');
bar_id.style.width = (percent + '%');
stage_id.title = (data['status']);
stage_id.value = (data['status']);
stage_id.innerHTML = (data['status']);
// if state is unexpected then end poll
if (data['state'] != 'PENDING' && data['state'] != 'PROGRESS' && data['state'] != "COMPLETE") {
stage_id.innerHTML = (data['state']);
// otherwise keep polling every 1.5 seconds
} else {
setTimeout(function() {
update_progress(status_url, index);
}, 1500);
}
})
.done(() => {}) // is this needed? I really don't know to be honest,
// maybe you can skip this right away
.fail(() => {
setTimeout(function() {
update_progress(status_url, index);
}, 1500); // we keep polling if the request fail!
})
}
You could separate your code on several callbacks, that would be executed on success, on failure, and other callback that would be executed always regardless of success or failure. You could then guess when the user clicks the submit button and disable it, then enable it again when the request is finished.
var ongoing = true; // Disable submit button
$.getJSON(status_url, function(data) {
// Your success code here
})
.fail(jqXHR, textStatus, errorThrown) {
// Your error/retry code here
})
.always(function() {
ongoing = false; // Enable submit button
});
Related
I'm writing a news display for the company I work at and I'm trying to get the page to refresh after it's looped through the entire length of a JSON array. Currently everything works, but I'm not entirely sure where the refresh command would go. Where it is at the moment is not executing. Here's my relevant code:
var i = 0,
d = null,
x = null,
interval = 3000;
console.log('welcome');
$(document).ready(function(){
fetch();
console.log('fetching');
});
function fetch(){
// get the data *once* when the page loads
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100), function(data){
// store the data in a global variable 'd'
d = data[0];
console.log('results');
console.log(data);
// create a recurring call to update()
x = setInterval(function(){
update()
}, interval);
});
}
function update(){
console.log('update starting');
// if there isn't an array element, reset to the first once
if (d && !d[i]){
console.log('got the D');
clearInterval(x);
i = 0;
fetch();
return;
if(d[i] >= d.length){
// refresh the window if the variable is longer than the array
console.log('refreshing');
window.location.reload();
}
}
// remove the previous items from the page
$('ul').empty();
// add the next item to the page
$('ul').append(
'<li>' + d[i]['news']
+ '</li>'
);
// increment for the next iteration
i++;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='news'>
<ul></ul>
</div>
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100), function(data){
//your code
}).done(function( data ) {
window.location.reload();
});
Function written in done section will be executed after complete all code from main function
I have figured this out going in a direction I wasn't originally planning to. I realized that once I included an RNG for version control to my .json file I actually didn't need to refresh the page. I am using an iterative loop, so once the loop completes it goes back to the top, and re-opens the info.json. My RNG looks like this:
$.getJSON('info.json?ver=' + Math.floor(Math.random() * 100) + '.' + Math.floor(Math.random() * 100), function(data){
//code here
}
This means that every time $.getJSON is caled, it's looking at a different version number than it was before, which forces a server check and download, which keeps things in the file updating to the screen on their own. Network sources looked like this:
info.json?ver=1.23
info.json?ver=2.42
info.json?ver=1.15
Definitely not the solution I was looking for, but it is actually a better solution than my question could have asked.
If I understood you question, you just need to know where your reload function is correct?
Well it's window.location.reload();
I'm trying to add a 1 second cooldown to my send-message system (as in, you can send 1 message per second max). So my initial thought was simply to create a timeout, and before attempting in sending to check if it exists still. That turned out to take more line of code than I anticipated initially.
Is there something I'm missing here? Isn't there something as simple as:
//inside some message sending function
if(!mySuperCooldown)
{
//send message
mySuperCooldown = cooldown(1000);
}
Everything else I construct successfully ends up taking loads of lines, and it appears to me as something someone thought of before. Thank you, and excuse my illiteracy.
Have a flag that allows messages, and set it to false when a message is sent. Then set a timeout for 1000 milliseconds that resets the flag to true.
var allowMessage = true;
function sendMessage(msg) {
if (allowMessage) {
//do something
allowMessage = false;
setTimeout(() => allowMessage = true, 1000);
}
}
Make a higher order function that turns a normal function into one that is rate limited:
function rate_limit(delay, func) {
var last_call = null;
return function() {
if (last_call && (Date.now() - last_call <= delay)) {
return;
}
last_call = Date.now();
return func();
};
}
You can then rate limit any function:
var my_function = rate_limit(1000, function() {
console.log('foo');
});
Running my_function() will only call your original function once per second.
In our application, we have some questions to answer that will update a progress bar. Currently, I have a function that waits for HTML Attribute changes which works for most things, but it's a little finicky for the progress bar since the animation occurs over 1-2 seconds as the bar moves from 0 - 10% etc. So the failure I'm currently facing is things like: Expected 11 to be within range 12, 14.
Code:
Util.prototype.waitForAttributeChange = function (el, attr, time) {
var timeout = time || 0,
currentAttr;
el.getAttribute(attr).then(function (val) {
currentAttr = val;
return currentAttr;
}).then(function () {
return browser.wait(function () {
return el.getAttribute(attr).then(function (val) {
return val !== currentAttr;
});
}, timeout);
});
};
Usage:
Util.waitForAttributeChange(Page.progressBar(), 'style', 10000).then(function () {
expect(Page.getProgressBarValue()).toBeWithinRange(12, 14);
};
Problem: The value grabbed is not the end result of the progress bar, it's still moving when it's grabbing it (because my function waits for Attribute changes, and the attribute did change at this point)
Question: Is there another way I can wait for an animation, specifically waiting for it to be completed? And/or is this possible without using browser.sleep()?
You might be able to solve this problem by using Expected Conditions.
I use the below methods whenever I need to wait for an element to be visible then wait for it to go away before executing the next step. This is helpful for temporary confirmation modals that may block interaction with other elements.
let waitTimeInSeconds = 15;
let EC = protractor.ExpectedConditions;
secondsToMillis(seconds) {
return seconds * 1000;
}
waitToBeVisible(element: ElementFinder) {
browser.wait(EC.visibilityOf(element), this.secondsToMillis(waitTimeInSeconds), 'The element \'' + element.locator() + '\' did not appear within ' + waitTimeInSeconds + ' seconds.');
}
waitToNotBeVisible(element: ElementFinder) {
browser.wait(EC.not(EC.visibilityOf(element)), this.secondsToMillis(waitTimeInSeconds), 'The element \'' + element.locator() + '\' still appeared within ' + waitTimeInSeconds + ' seconds.');
}
I'm using Devise, and automatic logout works great.
However, the user is not informed they have been logged out until they make another request, at which point they are redirected to the sign in page. For AJAX functionality, this is not great, it either fails silently or raises an exception.
Devise wiki doesnt seem to have an example, is there a standard solution to this? A javascript popup with a countdown timer, that does a redirect if the user doesnt click "keep me logged in"?
I ended up implementing jscript timeout something similar to below.
Some unanswered questions:
what happens on switching to another tab?
works in IE?
application.js
//= require timer
// redirect user after 15 minutes of inactivity - should match Devise.timeout_in + 1 second grace period
$(function() {
var logout_timer = new Timer(901, 'users/sign_in', window);
logout_timer.start();
// restart timer if activity
$(document).on('keyup keypress blur change mousemove',function(){
logout_timer.start();
});
});
timer.js
Timer = function(time_in_secs, path, windowobj) { // window object must be injected, else location replace will fail specs
var self = this; // 'this' not avail in setInterval, must set to local var avail to all functions
this.state = 'init'
this.time_remaining = time_in_secs;
this.timer_id = undefined;
this.start = function() {
// if restarting, there will be a timer id. Clear it to prevent creating a new timer, reset time remaining
if (this.timer_id !== undefined) {
this.time_remaining = time_in_secs;
this.clear_timer(this.timer_id, self);
}
this.state = 'running';
this.timer_id = setInterval(function() { // IE any version does not allow args to setInterval. Therefore, local variables or refer to self obj
self.time_remaining -= 1;
// log status every 10 seconds
if ((self.time_remaining % 10) === 0) {
console.log("logging user out in " + self.time_remaining + " seconds");
}
// when timer runs out, clear timer and redirect
if ( self.time_remaining <= 0 ) {
self.clear_timer(self.timer_id, self);
self.do_redirect(path, windowobj);
};
}, 1000);
return this.timer_id;
};
this.clear_timer = function(timer_id, self) {
self.state = 'stopped';
clearInterval(self.timer_id);
}
this.remaining = function() {
return this.time_remaining;
};
this.do_redirect = function(path, windowobj) {
console.log("Redirecting to " + path);
self.state = 'redirecting';
windowobj.location = path;
}
}
I've been trying to recreate a countdown timer similar to the one gmail uses that when you get disconnected from internet. The ajax request fails then it begin a short countdown then makes another ajax request and if it fails again then begin a longer countdown and so forth. Once a determined countdown value is reached (lets say 1 minute), the countdown is maintained at 1 minute until the internet connection is recovered or the servers comes back.
I don't want to use a plugin because this code will be embedded in a micro-controller that has limited space and also prefer not to place it as external file for practical reasons, even though jQuery library will already be external.
Everything should trigger onload, and continue automatically (i.e. no control inputs will be used).
So far I've developed code that does what I want if the ajax request succeeds or fails immediately but if there is a latency on the ajax request status (as for example having the server down) the browser won't produce an immediate result and my code fails.
I know what I stated above because I actually took the server down and was been help by firebug on Mozilla Firefox to see that the ajax result (success or failure) wasn't triggered immediately but keep waiting for several seconds.
Help please!
html code:
<div id='tempFail' ></div>
jQuery code:
$(document).ready(function()
{
//do when DOM ready - http://api.jquery.com/ready/
var timerSpeed = [1000, 5000, 10000, 20000, 30000, 60000];
// current time in ms since 1/1/1970, plus the initial reload interval
var end = (new Date).getTime() + timerSpeed[1];
var n=0;
var m=0;
setInterval(function()
{
var now = (new Date).getTime();
// Current time left to re-load in seconds, sometimes it goes to negative values, see below
var secLeft = Math.floor(( end - now ) / 1000);
// if secLeft is negative multiply by zero...equal to secLeft=0, this will produce an error of one second approximately
var timeToload = (secLeft < 0) ? secLeft * 0 : secLeft;
if (n!=0)
{
//check for failed or delayed request\n\
$('#tempFail').html('Failed or delayed response. Will auto load in: '+timeToload+ ' seconds!');
}
if( (secLeft)<=0)// if reload time is reached do
{
if (m==0)//used to prevent multiple continue reloads when ajax request status is not yet defined
{
m=1;
$.getScript('script_vars.js').done(function (data)
{
//if request succeeded
m=0;
n = 0;
end = (new Date).getTime() + timerSpeed[1];
// Time to load after the initial interval set above
$('#tempFail').html('');
//other code on success here
})
.fail(function()
{
//if request failed
m=0;
n ++;
if(n==6) n=5;
switch(n){ //timer delay for failed request\n\
case 1:
end = (new Date).getTime() + timerSpeed[1];
break;
case 2:
end = (new Date).getTime() + timerSpeed[2];
break;
case 3:
end = (new Date).getTime() + timerSpeed[3];
break;
case 4:
end = (new Date).getTime() + timerSpeed[4];
break;
case 5:
end = (new Date).getTime() + timerSpeed[5];
break;
}
});
}
}
}, 1000);
});
You asked for an example so I've written the following, you may want to wrap the contents of the function within another function so you can repeat it/don't have to worry about namespaces/etc. Didn't test so don't expect bug free!
Using window.setTimeout for every action, separated each stage into it's own function so code paths can more easily be followed.
$(document).ready(function () { // http://api.jquery.com/ready/
var $tempFail = $('#tempFail'),
delay = [1000, 5000, 10000, 20000, 30000, 60000],
delay_index = 0,
delay_ends = 0,
inform_user_ref = null,
inform_user = function inform_user() {
var now = (new Date).getTime(),
delta; // for difference, calculate later
if (delay_ends > now) { // if we're waiting for a retry
delta = Math.floor((delay_ends - now ) / 1000); // calculate time to wait
$tempFail.html('Failed or delayed response. Will auto load in: '+delta+ ' seconds!'); // let people know
window.setTimeout(inform_user, 200); // loop countdown timer
// can fast refresh this as it's just a countdown
}
},
get_success = function () {
$tempFail.html('');
// .. code on success
},
get_fail = function () {
delay_index < 5 && ++delay_index; // increment delay_index
get_initialise(); // retry
window.clearTimeout(inform_user_ref); // remove any old countdown timer
inform_user_ref = inform_user(); // and display new countdown
},
get_try = function () {
$.getScript('script_vars.js')
.done(get_success)
.fail(get_fail);
},
get_initialise = function () {
delay_ends = (new Date).getTime() + delay[delay_index];
window.setTimeout(get_try, delay[delay_index]); // retry
};
get_initialise(); // initial
});
Wow! Mr Paul S. your code was crazy good. I just made a couple of adjustments to have it perfectly working as I need it.
Added the following on ajax success:
delay_index = 0; //reset delay_index
get_initialise(); // retry
so I keep the code running every 5 seconds if everything goes ok.
2.
Added two new variables: let_cntDwn_end and ajax_rqst_status to avoid countdown number jumping (to let countdown finish before beginning the next one ) and to display a message while the ajax request haven't given any result respectively.
Here is the new code:
$(document).ready(function(){ //do when DOM ready - http://api.jquery.com/ready/
var $tempFail = $('#tempFail'),
delay = [5000, 5000, 10000, 20000, 30000, 60000],
delay_index = 0,
delay_ends = 0,
inform_user_ref = null,
let_cntDwn_end = 0,
ajax_rqst_status = 0, //ajax success or failure triggered
inform_user = function inform_user() {
var now = (new Date).getTime(),
delta; // for difference, calculated later
if (delay_ends > now) { // if we're waiting for a retry
let_cntDwn_end = 1;
delta = Math.floor((delay_ends - now ) / 1000); // calculate time to wait
if (ajax_rqst_status==0){
$tempFail.html('Failed response. Will auto load in: '+delta+ ' seconds!'); // let people know
window.setTimeout(inform_user, 900); // loop countdown timer
// can fast refresh this as it's just a countdown
}
}
else {let_cntDwn_end = 0; get_try();}
},
get_success = function () {
ajax_rqst_status =0;
$tempFail.html('');
// .. code on success
delay_index = 0; //reset delay_index
get_initialise(); // retry
},
get_fail = function () {
ajax_rqst_status =0;
delay_index < 5 && ++delay_index; // increment delay_index
get_initialise(); // retry
window.clearTimeout(inform_user_ref); // remove any old countdown timer
inform_user_ref = inform_user(); // and display new countdown
},
get_try = function () {
if (let_cntDwn_end == 0){
ajax_rqst_status=1;
$tempFail.html('Waiting for Ajax request success or failure'); // let people know
$.getScript('script_vars.js')
.done(get_success)
.fail(get_fail);
}
},
get_initialise = function () {
delay_ends = (new Date).getTime() + delay[delay_index];
window.setTimeout(get_try, delay[delay_index]); // retry
};
get_initialise(); // initial
});
There's also JS lib which handles this for you by monitoring ajax requests.
https://github.com/HubSpot/offline