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
Related
I am working on a tool that require to measure certain amount of time (for example 2 minutes) since user interaction with a tool, this time also needs to be displayed for the user.
I started simple server loop for this reason:
setInterval(function() {
measure.time();
}, 10);
i created a code for to calculate:
this.reset = 0;
this.step = 100 // 10 ms server loop, adds 1 to reset === 100 is 1 second
this.time = function() {
if(this.reset === this.step) {
console.log('SPAWN', new Date());
this.reset = 0;
}
this.reset++;
};
this is the result of my approach:
The idea is to show clock for the user and update it every second, but it skips seconds rather often, what am i doing wrong?
I'm using katspaugh's waveSurfer library for playing sound files.
I wrote code to show 'elapsed time / total time' in this way.
waveSurfer.on('play', function() {
$scope.getCurrentDuration();
});
$scope.getCurrentDuration() is a function to transform floating-type variable to string-type variable, like below:
$scope.getDuration = function() {
if ($scope.waveSurferReady) {
var time = waveSurfer.getDuration(); // get duration(float) in second
var minute = Math.floor(time / 60); // get minute(integer) from time
var tmp = Math.round(time - (minute * 60)); // get second(integer) from time
var second = (tmp < 10 ? '0' : '') + tmp; // make two-figured integer if less than 10
return String(minute + ':' + second); // combine minute and second in string
}
else {
return 'not ready yet'; // waveSurfer is not ready yet
}
};
But the problem is,
in this part:
waveSurfer.on('play', function() ...)
the callback function execute only once.
I expect the callback function called periodically, but it executes only once, so as the result, elapsed time is shown only at the start time.
How can I solve this?
Looking into the source, I've found the audioprocess event paired with html5 timeupdate event.
Try it out.
waveSurfer.on('audioprocess', function() {
// do stuff here
});
I'm trying to loop through a date range and delay going to the next iteration by 1 second. Each time I run the following in either jsFiddle or Plunker the browser crashes.
var current_date = new Date("01/13/2013");
var end_date = new Date("01/20/2013");
var end_date_time = end_date.getTime();
while (current_date.getTime() < end_date_time) {
setTimeout(function () {
console.log(current_date);
current_date.setDate(current_date.getDate()+1);
}, 1000);
}
Could anyone point me in the right direction as to why this isn't working and how to fix it?
You have a blocking loop here. It blocks the whole browser (possibly forever!).
while (current_date.getTime() < end_date_time) {
// do something (it doesn't matter what)
}
What you need is setInterval:
var current_date = new Date("01/13/2013");
var end_date = new Date("01/20/2013");
var end_date_time = end_date.getTime();
var interval = setInterval(function () {
if (current_date.getTime() >= end_date_time) {
clearInterval(interval);
}
console.log(current_date);
current_date.setDate(current_date.getDate()+1);
}, 1000);
(I didn't check the code for correctness)
Why does it block the UI?
While javascript code is running, the user can't interact with the website and often with the whole browser.
When you look at what the browser was doing over time the while() approach looks like
[while][while][while][while][while][while][while][while][while][while][while]
the interval approach looks like
[interval] [interval] [interval]
Only in the free time the browser is able to do other things, for example handling user interaction.
You can simply call a timeout if a condition fails:
function incrementDate(currentDate, endDateTime) {
currentDate.setDate(currentDate.getDate()+1);
if (currentDate.getTime() < endDateTime) {
setTimeout(function() {
incrementDate(currentDate, endTime);
}, 1000);
}
}
incrementDate(current_date, end_date_time);
I'm attempting to allow a user to set an alarm from the client and pass it to the server. The server then has a setTimeout that counts down and when time runs out, executes the function.
This first part is working fine, however, I need the the ability to clear that same timeout, should the client decide to cancel that particular alarm.
Note: I've been storing various data using Redis, so that is available.
var client = redis.createClient();
io.set("store", new sio.RedisStore);
io.sockets.on('connection', function (socket) {
socket.on('alarm:config', function(list, date, time, bool) {
if (bool) {
var now = new Date().getTime();
var year = date[0],
month = date[1] - 1,
day = date[2];
var hour = time[0],
minutes = time[1];
var alarm = new Date(year, month, day, hour, minutes);
var countdown = alarm - now;
var myAlarm = setTimeout(function() {
// do stuff...
}, ( countdown ) );
} else {
clearTimeout(myAlarm);
}
});
});
The approach I have in mind is that I would use the boolean value to determine if the user is setting or canceling that particular alarm. I realize that setting a local variable "myAlarm" will not work, I just put it there to convey the idea.
I am trying to figure out a way to store a reference to that exact timeout so that the next time the "alarm:config" socket event is triggered with a false boolean value, it can cancel the timeout that was set earlier.
It might be another question all together, but how does an application like Google Calendar store a date and time and then know exactly when to trigger it as well as offer the ability to cancel it? This would essentially be the same idea.
UPDATE: I have it working using the following solution. I am open to a more elegant solution.
socket.on('alarm:config', function(list, date, time, bool) {
var alarmName = "timer:" + list;
if (bool) {
client.hset(alarmName, "status", true);
var now = new Date().getTime();
var year = date[0],
month = date[1] - 1,
day = date[2];
var hour = time[0],
minutes = time[1];
var alarm = new Date(year, month, day, hour, minutes);
var countdown = alarm - now;
setTimeout(function() {
client.hget(alarmName, "status", function(err, bool) {
if(bool == 'true') {
// do stuff...
} else {
console.log("This alarm has been canceled.");
}
});
}, ( countdown ) );
} else {
console.log('canceling alarm');
client.hset(alarmName, "status", false);
}
});
Depending on how large of an application you're building, there are a couple of options.
Processing Queue
You could restructure your application to use a job queue instead of simply setting timers. This has an advantage that you can split it in the future into multiple processes, but it does complicate the handling a bit.
A library like Kue uses just Redis and allows you to do a delayed put to set events in the future.
Going from the Kue readme:
var kue = require('kue')
, jobs = kue.createQueue();
// Create delayed job (need to store email.id in redis as well)
var email = jobs.create('email', {
title: 'Account renewal required',
to: 'tj#learnboost.com',
template: 'renewal-email'
}).delay(minute).save();
// Process job
jobs.process('email', function(job, done){
email(job.data.to, done);
});
// Cancel job
email.remove(function(err){
if (err) throw err;
console.log('removed completed job #%d', job.id);
});
Storing Reference to Timeout
This is much less robust, but could allow you to do it very simply. It leaves global variables floating around, and has no recovery if the process crashes.
Basically, you store the timer in a global variable and cancel it based on the job id.
var myAlarms = {}
socket.on('alarm:config', function(list, date, time, bool) {
var alarmName = "timer:" + list;
if (bool) {
myAlarms[alarmName] = setTimeout(function() {
// do stuff...
}, countdown);
} else {
clearTimeout(myAlarms[alarmName]);
}
}
Note: None of this code has been tested and it likely contains errors.
Im trying to show on my site changeable clock synchronized with facebook server.
The fb server time is available at:
https://api.facebook.com/method/fql.query?query=SELECT+now%28%29+FROM+link_stat+WHERE+url+%3D+%271.2%27&format=json
How to make it changeable every second without refreshing the page?
Assuming some non-written functions, it should look like that:
var requestBegin = Date.now();
getServertimeFromFacebook(function callback(fbTime) {
var requestEnd = Date.now();
var latency = (requestEnd - requestBegin) / 2;
var curDevicetime = Date.now(); // = requestEnd, of course
var difference = fbTime - latency - curDeviceTime;
function clock() {
var cur = Date.now();
var curFbTime = cur + difference;
show(curFbTime); // print, log, whatever
};
setInterval(clock, …); // you could use a self-adjusting clock
// by using a setTimeout for each tick
});
You could do
show = function(t) { console.log(new Date(t).toString()); };
getServertimeFromFacebook = function(cb) {
ajax("https://api.facebook.com/method/fql.query?query=SELECT+now%28%29+FROM+link_stat+WHERE+url+%3D+%271.2%27&format=json", function(responsetext) {
var obj = JSON.parse(responsetext);
var ts = obj[0].anon,
tms = ts * 1000;
cb(tms);
});
};
I wouldn't call the API every second.
Instead, I would get the Facebook server time only one time at the beginning. And then, I would increment my time value every second by looping using javascript :
setTimeout(function() { /* increment time */ }, 1000);
Bergi: [{"anon":1354654854}] is a unix time. Indeed, Facebook often (always?) deals with time using this representation.