in my Ionic app, I implemented the geolocation watchPosition with the following options. My options are set to fire every 10 seconds, but its firing every second. And often it fires two times per second.
function watchPosition() {
var options = {
maximumAge: 10000,
timeout: 10000,
enableHighAccuracy: true,
}
watchID = navigator.geolocation.watchPosition(onSuccess, onError, options);
function onSuccess(pos) {
...
}
function onError(error) {
...
}
}
And when my device is ready, I trigger the watchPosition function with an addListener:
Original call was like this:
var watchID = null ;
document.addEventListener("deviceready", watchPosition);
I then changed to this:
document.addEventListener("deviceready",setupWatch) ;
function setupWatch() {
// global var here so it can be cleared on logout (or whenever).
activeWatch = setInterval(watchPosition, 5000);
}
And it is still firing every second.
Then I replaced the addEventListener simply with, I realized the eventListener was already inside of Ionics $ionicPlatform.ready() { .. } thus an eventListener wasn't needed. However, just calling this now triggers the watchPosition like 5 times per second, not once per 5 seconds.:
setupWatch() ;
Additional question regarding best practices for watchPosition. What is the ideal timeout for continuing to call this function...is every second OK, is it too much load/processing, does it drain batteries faster, etc?
The timeout and maximumAge you're using are not defining how frequently you have an answer from the browser. Here is what MDN says about these properties:
PositionOptions.timeout Is a positive long value representing the maximum length of time (in milliseconds) the device is allowed to take in order to return a position.
PositionOptions.maximumAge Is a positive long value indicating the maximum age in milliseconds of a possible cached position that is acceptable to return.
source
I would advice you to store the value from the callback in a variable and user another setInterval to return the value at a rate you can define.
EDIT
If you want to handle the rate of answer, you can use the function getCurrentPosition, that will fire the callback only once (source)
In my code the watchPosition was firing twice in succession but the getCurrentPostion always just fires once for obvious reasons.
Solution 1 would be to use getCurrentPosition, and then set a timer to re-check that position X milliseconds later and update as required.
Solution 2 (which I used, for various other reasons) would be to use watchPosition but have the callback double-check that the long/lat has actually changed. If not then ignore the rest of the function. So as an example:
var oldlong = "";
var oldlat = "";
navigator.geolocation.watchPosition(onGpsChangeSuccess,onGpsError,{
maximumAge: 10000,
enableHighAccuracy: false,
timeout: 15000
}
);
var onGpsChangeSuccess = function(){
//Watch was triggered...
if(oldlong != position.coords.longitude && oldlat!=position.coords.longitude){
//Changes detected
oldlong = position.coords.longitude;
oldlat = position.coords.latitude;
performTheRestOfYourFunctionHere
}else{
//No changes detected
}
}
We shouldn't have to perform this additional check, but if the watcher has a bug then it's better to be safe.
I would be surprised that your successive Lat/Lng readings are identical. Perhaps the "accuracy" meters have varied or one source was GPS and another Telco Tower. Outdoors or indoors?
Regardless I recommend filtering readings as appropriate for your App. Ignoring inaccurate or uninteresting readings is IMHO something ALL GPS Apps have to cater for.
Related
I've got a test app that fetches lat/lng values from the HTML5 geolocation service. Unfortunately, it's firing at rather inconsistent time intervals, anywhere from 500ms to 10000ms. I've tried changing the maximimAge and timeout parameters for the watch but those don't seem to change anything. I'm testing it in Chrome as well as via a simple Cordova app on an Android Lollipop build. The code below simply displays the timestamp value of the watch to eliminate any other delays that could be causing the issue. It appears that the interval is close to a 1 second then 5 second repeating pattern. I've also tried placing the geolocation fetch function inside a setInterval function and it behaves with the same 1 and 5 second repeating interval.
<html>
<body>
<h1>Timestamp</h1>
<div id="mytimestamp"></div>
<button id="btnstopwatch">Stop GPS</button>
</body>
</html>
<script type="text/javascript">
var mytimestamp = document.getElementById("mytimestamp");
//start watching location
watchPositionId = navigator.geolocation.watchPosition(updateCompass,handleerror,{
enableHighAccuracy:true,
maximumAge:3000,
timeout:3000
});
function updateCompass(p)
{
mytimestamp.innerHTML = p.timestamp;
}
function handleerror(err)
{
if(err.code ==1)
{
//user said no
alert('Please allow access to GPS');
}
else
{
alert(err.code);
}
}
</script>
Relating to watchPosition(), on the following link w3 says "the successCallback is only invoked when a new position is obtained .......implementations may impose limitations on the frequency of callbacks so as to avoid inadvertently consuming a disproportionate amount of resources."
What it means is that watchPosition() is waiting for events which will cause changes in position to happen, otherwise it does nothing. So watchPosition() will not be calling updateCompass() unless it sees the changes in position.
Also, in regard watchPosition(), maximumAge and timeout parameters are related to the acquiring new position, and these parameters has nothing to do with how much times and when watchPosition() is called...
WatchPosition() prevents continuous calls to see if position changes or not, and reacts only if some events related to position changes occur, this help to save resources like battery. But if you really want to see the position, let's say every three second, no matter if position changed or not I think setting an interval will be good approach.
var watchPositionId;
//start watching location
setInterval(function () {
watchPositionId = navigator.geolocation.watchPosition(updateCompass, handleerror, {
enableHighAccuracy: true,
maximumAge: 3000,
timeout: 3000
});
}, 3000);
function updateCompass(p) {
mytimestamp.innerHTML = p.timestamp;
// if you have alert message it will counts seconds once you dismiss the alert,
//this may be part of the issue why you kept getting diffrent intervals.
//alert(p.timestamp);
navigator.geolocation.clearWatch(watchPositionId);
}
You mentioned that you tried to setInterval and it didn't work, so please note that I commented alert() in the code above because it caused me to have delays, without it the time stamp in html updates every 3000ms with an accuracy about 5ms.
The above approach works if you want to use watchPosition() because clearWatch() stoppes the watchPosition() and it further callbacks. Basically the code above initiates new watchPosition() every three seconds and kills it with clearWatch(). That is being said I think watchPosition() is not the best method to use in this approach unless you expect significant changes in position within this time interval. I would rather use getCurrentPosition() since it does the job and doesn't wait for changes in position.
var watchPositionId;
//start watching location
setInterval(function () {
watchPositionId = navigator.geolocation.getCurrentPosition(updateCompass, handleerror, {
enableHighAccuracy: true,
maximumAge: 3000,
timeout: 3000
});
}, 3000);
function updateCompass(p) {
mytimestamp.innerHTML = p.timestamp;
}
I ended up changing the solution to use both geolocation as well as the deviceOrientationEvent. Using the geolocation event alone created a very 'jerky' response and the device orientation event smooths this out.
Here's the setInterval function set to fetch the geolocation every ten seconds:
window.onload = function()
{
//fetch new GPS info every 10000 milliseconds
window.setInterval(function ()
{
navigator.geolocation.getCurrentPosition(onGeoSuccess, onGeoFailure, {
enableHighAccuracy: true,
maximumAge: 0,
timeout: 30000 //large timeout to accomodate slow GPS lock on some devices
});
}, 10000); //update current location every ten seconds
};
On geo success:
function onGeoSuccess(location)
{
//call function to calculate bearing
var cluebearing = geo.bearing(currentlat, currentlng, cluelat, cluelng);
//call function to rotate arrow
rotateArrow(cluebearing);
}//end onGeoSuccess()
Function to fetch device orientation and process:
function rotateArrow(mybearing)
{
//Check for support for DeviceOrientation event
if(window.DeviceOrientationEvent)
{
//orientation listener
window.addEventListener('deviceorientation', function(event)
{
var myalpha = event.alpha; //rotation from north
//process orientation change
}//end deviceorientation listener
}
}//end rotateArrow()
On geo failure:
function onGeoFailure()
{
alert('Please turn on your phone\'s GPS');
}
I have read from multiple places that setTimeout() is preferable to setInterval() when setting something up to basically run forever. The code below works fine but after about an hour of running Firefox (38.0.1) throws an error of too much recursion.
Essentially I have it grabbing a very small amount of text from counts.php and updating a table with that information. The whole call and return takes about 50ms according to the inspectors. I'm trying to have it do this every x seconds as directed by t.
I suspect if I switch to setInterval() this would probably work, but I wasn't sure what the current state of the setTimeout() vs setInterval() mindset is as everything I've been finding is about 3-5 years old.
$(document).ready(function() {
t = 3000;
$.ajaxSetup({cache: false});
function countsTimer(t) {
setTimeout(function () {
$.getJSON("counts.php", function (r) {
$(".count").each(function(i,v) {
if ($(this).html() != r[i]) {
$(this).fadeOut(function () {
$(this)
.css("color", ($(this).html() < r[i]) ? "green" : "red")
.html(r[i])
.fadeIn()
.animate({color: '#585858'}, 10000);
})
};
});
t = $(".selected").html().slice(0,-1) * ($(".selected").html().slice(-1) == "s" ? 1000 : 60000);
countsTimer(t);
});
}, t);
};
countsTimer(t);
});
Update: This issue was resolved by adding the .stop(true, true) before the .fadeOut() animation. This issue only occurred in Firefox as testing in other browsers didn't cause any issues. I have marked the answer as correct in spite of it not being the solution in this particular case but rather it offers a good explanation in a more general sense.
You should indeed switch to setInterval() in this case. The problem with setInterval() is that you either have to keep a reference if you ever want to clear the timeout and in case the operation (possibly) takes longer to perform than the timeout itself the operation could be running twice.
For example if you have a function running every 1s using setInterval, however the function itself takes 2s to complete due to a slow XHR request, that function will be running twice at the same time at some point. This is often undesirable. By using setTimout and calling that at the end of the original function the function never overlaps and the timeout you set is always the time between two function calls.
However, in your case you have a long-running application it seems, because your function runs every 3 seconds, the function call stack will increase by one every three seconds. This cannot be avoided unless you break this recursion loop. For example, you could only do the request when receiving a browser event like click on the document and checking for the time.
(function()
{
var lastCheck = Date.now(), alreadyRunning = false;
document.addEventListener
(
"click",
function()
{
if(!alreadyRunning && Date.now() - lastCheck > 3000)
{
alreadyRunning = true;
/* Do your request here! */
//Code below should run after your request has finished
lastCheck = Date.now();
alreadyRunning = false;
}
}
)
}());
This doesn't have the drawback setInterval does, because you always check if the code is already running, however the check only runs when receiving a browser event. (Which is normally not a problem.) And this method causes a lot more boilerplate.
So if you're sure the XHR request won't take longer than 3s to complete, just use setInterval().
Edit: Answer above is wrong in some aspects
As pointed out in the comments, setTimeout() does indeed not increase the call stack size, since it returns before the function in the timeout is called. Also the function in the question does not contain any specific recursion. I'll keep this answer because part of the question are about setTimeout() vs setInterval(). However, the problem causing the recursion error will probably be in some other piece of code since there is not function calling itself, directly or indirectly, anywhere in the sample code.
I have written a custom animation function. It usually works just fine, but when I call animate(); in rapid succession with different endCallbacks, sometimes the callbacks overlap really badly, causing the wrong action at the wrong time.
The problem is that the function instantiates multiple times and executes untill the endValue is reached. The currentValue is changed so fast that I get to see just the last value in my html page animation. This hiddes this unwanted behavior.
What I need when I call animate(); a second time is to end the first instance of animate(); and trigger a new one with new values and a new callback. Also at the same time I want to stop the setTimeout() function just to make sure no wrong callback is triggered.
window.onload = function(){
document.addEventListener('click', // some button
function (){
animate(1, 10);
}, false
);
}
function animate(startValue, endValue, callback, endCallback) {
var startValue = startValue,
currentValue = startValue,
endValue = endValue,
callback = callback,
timeout = null;
loopAnimation();
function loopAnimation(){
if (currentValue != endValue){
timeout = setTimeout(function(){
currentValue++;
// Callback executes some page manipulation code
if (typeof callback !== "undefined") callback(currentValue);
console.log(currentValue);
loopAnimation();
},500)
} else {
console.log("This callback triggers some specific changes in my page");
if (typeof endCallback !== "undefined") endCallback();
}
}
}
Instead of seeing in the console:
1,2,3, - 1,4,2,5 ... 6,9,7,10,8,9,10
I'd like to see just:
1,2,3, - 1,2 ... 7,8,9,10
However, keep in mind that because of the way I use animate() in my script I can't relly on knowing the name or scope of the input variables. This cuts me from being able to solve it myself.
While it isn't quite the implementation you're asking for, I wonder if Underscore's throttle or debounce would meet the need?
debounce will make sure your function is called no more than X times per second -- it'll still be executed once per every time called, but the subsequent calls will be delayed to meet your rate limit. So if you called animate twice in quick succession, debounce can delay the second execution until 100ms after the first or what have you.
throttle will basically ignore calls that occur during the rate limit. So if you call your animate 10 times within 100ms, you could have it throw out all but the first. (Actually, it'll do the first one, plus one at at the end of the wait period).
You don't need to use all of underscore to get these methods; I've seen people frequently copy and pasting just the debounce and/or throttle functions from underscore. If you google, you can find some standalone throttle or debounce implementations.
Throttle and debounce are commonly used in just your case, animation.
For your original spec, to actually "end the first instance of animate()" -- there's no great reliable way to do that in javascript. There's no real general purpose way to 'cancel' a function already being executed. If you can make it work with debounce or throttle, I think it will lead to less frustration.
What you need is to store the last timeout id you used. So next time you start a new animation, you clear any ongoing animation using this timeout id and clearTimeout.
I found convenient to store the interval on the function itself.
See the jsbin here :
http://jsbin.com/nadawezete/1/edit?js,console,output
window.onload = function(){
document.addEventListener('click', // some button
function (){
animate(1, 10);
}, false
);
};
function animate(startValue, endValue, callback, endCallback) {
var currentValue = startValue;
if (animate.timeout) clearTimeout(animate.timeout);
loopAnimation();
function loopAnimation(){
if (currentValue != endValue){
animate.timeout = setTimeout(function(){
console.log(currentValue);
currentValue++;
// Callback executes some page manipulation code
if (callback ) callback(currentValue);
loopAnimation();
},500);
} else {
console.log("This callback triggers some specific changes in my page");
if (endCallback) endCallback();
}
}
}
This article hit the top of HackerNews recently: http://highscalability.com/blog/2013/9/18/if-youre-programming-a-cell-phone-like-a-server-youre-doing.html#
In which it states:
The cell radio is one of the biggest battery drains on a phone. Every time you send data, no matter how small, the radio is powered on for up for 20-30 seconds. Every decision you make should be based on minimizing the number of times the radio powers up. Battery life can be dramatically improved by changing the way your apps handle data transfers. Users want their data now, the trick is balancing user experience with transferring data and minimizing power usage. A balance is achieved by apps carefully bundling all repeating and intermittent transfers together and then aggressively prefetching the intermittent transfers.
I would like to modify $.ajax to add an option like "doesn't need to be done right now, just do this request when another request is launched". What would be a good way to go about this?
I started with this:
(function($) {
var batches = [];
var oldAjax = $.fn.ajax;
var lastAjax = 0;
var interval = 5*60*1000; // Should be between 2-5 minutes
$.fn.extend({batchedAjax: function() {
batches.push(arguments);
}});
var runBatches = function() {
var now = new Date().getTime();
var batched;
if (lastAjax + interval < now) {
while (batched = batches.pop()) {
oldAjax.apply(null, batched);
}
}
}
setInterval(runBatches, interval);
$.fn.ajax = function() {
runBatches();
oldAjax.apply(null, arguments);
lastAjax = now;
};
})(jQuery);
I can't tell by the wording of the paper, I guess a good batch "interval" is 2-5 minutes, so I just used 5.
Is this a good implementation?
How can I make this a true modification of just the ajax method, by adding a {batchable:true} option to the method? I haven't quite figured that out either.
Does setInterval also keep the phone awake all the time? Is that a bad thing to do? Is there a better way to not do that?
Are there other things here that would cause a battery to drain faster?
Is this kind of approach even worthwhile? There are so many things going on at once in a modern smartphone, that if my app isn't using the cell, surely some other app is. Javascript can't detect if the cell is on or not, so why bother? Is it worth bothering?
I made some progress on adding the option to $.ajax, started to edit the question, and realized it's better as an answer:
(function($) {
var batches = [];
var oldAjax = $.fn.ajax;
var lastAjax = 0;
var interval = 5*60*1000; // Should be between 2-5 minutes
var runBatches = function() {
var now = new Date().getTime();
var batched;
if (lastAjax + interval < now) {
while (batched = batches.pop()) {
oldAjax.apply(null, batched);
}
}
}
setInterval(runBatches, interval);
$.fn.ajax = function(url, options) {
if (options.batchable) {
batches.push(arguments);
return;
}
runBatches();
oldAjax.apply(null, arguments);
lastAjax = now;
};
})(jQuery);
That was actually fairly straightforward. Is love to see a better answer though.
Does setInterval also keep the phone awake all the time? Is that a bad thing to do? Is there a better way to not do that?
From an iPhone 4, iOS 6.1.0 Safari environment:
A wrote an app with a countdown timer that updated an element's text on one-second intervals. The DOM tree had about medium complexity. The app was a relatively-simple calculator that didn't do any AJAX. However, I always had a sneaking suspicion that those once-per-second reflows were killing me. My battery sure seemed to deplete rather quickly, whenever I left it turned-on on a table, with Safari on the app's webpage.
And there were only two timeouts in that app. Now, I don't have any quantifiable proof that the timeouts were draining my battery, but losing about 10% every 45 minutes from this dopey calculator was a little unnerving. (Who knows though, maybe it was the backlight.)
On that note: You may want to build a test app that does AJAX on intervals, other things on intervals, etc, and compare how each function drains your battery under similar conditions. Getting a controlled environment might be tricky, but if there is a big enough difference in drain, then even "imperfect" testing conditions will yield noticeable-enough results for you to draw a conclusion.
However, I found out an interesting thing about how iOS 6.1.0 Safari handles timeouts:
The timeouts don't run their callbacks if you turn off the screen.
Consequentially, long-term timeouts will "miss their mark."
If my app's timer was to display the correct time (even after I closed and reopened the screen), then I couldn't go the easy route and do secondsLeft -= 1. If I turned off the screen, then the secondsLeft (relative to my starting time) would have been "behind," and thus incorrect. (The setTimeout callback did not run while the screen was turned off.)
The solution was that I had to recalculate timeLeft = fortyMinutes - (new Date().getTime() - startTime) on each interval.
Also, the timer in my app was supposed to change from green, to lime, to yellow, to red, as it got closer to expiry. Since, at this point, I was worried about the efficiency of my interval-code, I suspected that it would be better to "schedule" my color changes for their appropriate time (lime: 20 minutes after starting time, yellow: 30 mins, red: 35) (this seemed preferable to a quadruple-inequality-check on every interval, which would be futile 99% of the time).
However, if I scheduled such a color change, and my phone's screen was turned off at the target time, then that color change would never happen.
The solution was to check, on each interval, if the time elapsed since the last 1-second timer update had been ">= 2 seconds". (This way, the app could know if my phone had had its screen turned off; it was able to realize when it had "fallen behind.") At that point, if necessary, I would "forcibly" apply a color change and schedule the next one.
(Needless to say, I later removed the color-changer...)
So, I believe this confirms my claim that
iOS 6.1.0 Safari does not execute setTimeout callback functions if the screen is turned off.
So keep this in mind when "scheduling" your AJAX calls, because you will probably be affected by this behavior as well.
And, using my proposition, I can answer your question:
At least for iOS, we know that setTimeout sleeps while the screen is off.
Thus setTimeout won't give your phone "nightmares" ("keep it awake").
Is this kind of approach even worthwhile? There are so many things going on at once in a modern smartphone, that if my app isn't using the cell, surely some other app is. Javascript can't detect if the cell is on or not, so why bother? Is it worth bothering?
If you can get this implementation to work correctly then it seems like it would be worthwhile.
You will incur latency for every AJAX request you make, which will slow down your app to some degree. (Latency is the bane of page loading time, after all.) So you will definitely achieve some gain by "bundling" requests. Extending $.ajax such that you can "batch" requests will definitely have some merit.
The article you've linked clearly focuses on optimizing power consumption for apps (yes, the weather widget example is horrifying). Actively using a browser is, by definition, a foreground task; plus something like ApplicationCache is already available to reduce the need for network requests. You can then programmatically update the cache as required and avoid DIY.
Sceptical side note: if you are using jQuery as part of your HTML5 app (perhaps wrapped in Sencha or similar), perhaps the mobile app framework has more to do with request optimization than the code itself. I have no proof whatsoever, but goddammit this sounds about right :)
How can I make this a true modification of just the ajax method, by
adding a {batchable:true} option to the method? I haven't quite
figured that out either.
A perfectly valid approach but to me this sounds like duck punching gone wrong. I wouldn't. Even if you correctly default batchable to false, personally I would rather use a facade (perhaps even in its own namespace?)
var gQuery = {}; //gQuery = green jQuery, patent pending :)
gQuery.ajax = function(options,callback){
//your own .ajax with blackjack and hooking timeouts, ultimately just calling
$.ajax(options);
}
Does setInterval also keep the phone awake all the time? Is that a
bad thing to do? Is there a better way to not do that?
Native implementations of setInterval and setTimeout are very similar afaik; think of the latter not firing while the website is in the background for online banking inactivity prompts; when a page is not in the foreground its execution is basically halted. If an API is available for such "deferrals" (the article mentions of some relevant iOS7 capabilities) then it's likely a preferable approach, otherwise I see no reason to avoid setInterval.
Are there other things here that would cause a battery to drain
faster?
I'd speculate that any heavy load would (from calculating pi to pretty 3d transitions perhaps). But this sounds like premature optimization to me and reminds me of an e-reader with battery-saving mode that turned the LCD screen completely off :)
Is this kind of approach even worthwhile? There are so many things
going on at once in a modern smartphone, that if my app isn't using
the cell, surely some other app is. Javascript can't detect if the
cell is on or not, so why bother? Is it worth bothering?
The article pointed out a weather app being unreasonably greedy, and that would concern me. It seems to be a development oversight though more than anything else, as in fetching data more often than it's really needed. In an ideal world, this should be nicely handled on OS level, otherwise you'd end up with an array of competing workarounds. IMO: don't bother until highscalability posts another article telling you to :)
Here is my version:
(function($) {
var batches = [],
ajax = $.fn.ajax,
interval = 5*60*1000, // Should be between 2-5 minutes
timeout = setTimeout($.fn.ajax, interval);
$.fn.ajax=function(url, options) {
var batched, returns;
if(typeof url === "string") {
batches.push(arguments);
if(options.batchable) {
return;
}
}
while (batched = batches.shift()) {
returns = ajax.apply(null, batched);
}
clearTimeout(timeout);
timeout = setTimeout($.fn.ajax, interval);
return returns;
}
})(jQuery);
I think this version has the following main advantages:
If there is a non-batchable ajax call, the connection is used to send all batches. This Resets the timer.
Returns the expected return value on direct ajax calls
A direct processing of the batches can be triggered by calling $.fn.ajax() without parameters
As far as hacking the $.ajax method, I would :
try to also preserve the Promise mechanism provided by $.ajax,
take advantage of one of the global ajax events to trigger ajax calls,
maybe add a timer, to have the batch being called anyways in case no "immediate" $.ajax call is made,
give a new name to this function (in my code : $.batchAjax) and keep the orginal $.ajax.
Here is my go :
(function ($) {
var queue = [],
timerID = 0;
function ajaxQueue(url, settings) {
// cutom deferred used to forward the $.ajax' promise
var dfd = new $.Deferred();
// when called, this function executes the $.ajax call
function call() {
$.ajax(url, settings)
.done(function () {
dfd.resolveWith(this, arguments);
})
.fail(function () {
dfd.rejectWith(this, arguments);
});
}
// set a global timer, which will trigger the dequeuing in case no ajax call is ever made ...
if (timerID === 0) {
timerID = window.setTimeout(ajaxCallOne, 5000);
}
// enqueue this function, for later use
queue.push(call);
// return the promise
return dfd.promise();
}
function ajaxCallOne() {
window.clearTimeout(timerID);
timerID = 0;
if (queue.length > 0) {
f = queue.pop();
// async call : wait for the current ajax events
//to be processed before triggering a new one ...
setTimeout(f, 0);
}
}
// use the two functions :
$(document).bind('ajaxSend', ajaxCallOne);
// or :
//$(document).bind('ajaxComplete', ajaxCallOne);
$.batchAjax = ajaxQueue;
}(jQuery));
In this example, the hard coded delay fo 5 seconds defeats the purpose of "if less than 20 seconds between calls, it drains the battery". You can put a bigger one (5 minutes ?), or remove it altogether - it all depends on your app really.
fiddle
Regarding the general question "How do I write a web app which doesn't burn a phone's battery in 5 minutes ?" : it will take more than one magic arrow to deal with that one. It is a whole set of design decisions you will have to take, which really depends on your app.
You will have to arbitrate between loading as much data as possible in one go (and possibly send data which won't be used) vs fetching what you need (and possibly send many small individual requests).
Some parameters to take into account are :
volume of data (you don't want to drain your clients data plan either ...),
server load,
how much can be cached,
importance of being "up to date" (5 minutes delay for a chat app won't work),
frequency of client updates (a network game will probably require lots of updates from the client, a news app probably less ...).
One rather general suggestion : you can add a "live update" checkbox, and store its state client side. When unchecked, the client should hit a "refresh" button to download new data.
Here is my go, it somewhat grew out of what #Joe Frambach posted but I wanted the following additions:
retain the jXHR and error/success callbacks if they were provided
Debounce identical requests (by url and options match) while still triggering the callbacks or jqXHRs provided for EACH call
Use AjaxSettings to make configuration easier
Don't have each non batched ajax flush the batch, those should be separate processes IMO, but thus supply an option to force a batch flush as well.
Either way, this sucker would mostly likely be better done as a separate plugin rather than overriding and affecting the default .ajax function... enjoy:
(function($) {
$.ajaxSetup({
batchInterval: 5*60*1000,
flushBatch: false,
batchable: false,
batchDebounce: true
});
var batchRun = 0;
var batches = {};
var oldAjax = $.fn.ajax;
var queueBatch = function(url, options) {
var match = false;
var dfd = new $.Deferred();
batches[url] = batches[url] || [];
if(options.batchDebounce || $.ajaxSettings.batchDebounce) {
if(!options.success && !options.error) {
$.each(batches[url], function(index, batchedAjax) {
if($.param(batchedAjax.options) == $.param(options)) {
match = index;
return false;
}
});
}
if(match === false) {
batches[url].push({options:options, dfds:[dfd]});
} else {
batches[url][match].dfds.push(dfd);
}
} else {
batches[url].push({options:options, dfds:[dfd]);
}
return dfd.promise();
}
var runBatches = function() {
$.each(batches, function(url, batchedOptions) {
$.each(batchedOptions, function(index, batchedAjax) {
oldAjax.apply(null, url, batchedAjax.options).then(
function(data, textStatus, jqXHR) {
var args = arguments;
$.each(batchedAjax.dfds, function(index, dfd) {
dfd.resolve(args);
});
}, function(jqXHR, textStatus, errorThrown) {
var args = arguments;
$.each(batchedAjax.dfds, function(index, dfd) {
dfd.reject(args);
});
}
)
});
});
batches = {};
batchRun = new Date.getTime();
}
setInterval(runBatches, $.ajaxSettings.batchInterval);
$.fn.ajax = function(url, options) {
if (options.batchable) {
var xhr = queueBatch(url, options);
if((new Date.getTime()) - batchRun >= options.batchInterval) {
runBatches();
}
return xhr;
}
if (options.flushBatch) {
runBatches();
}
return oldAjax.call(null, url, options);
};
})(jQuery);
So I made some timers for a quiz. The thing is, I just realized when I put
javascript: alert("blah");
in the address, the popup alert box pauses my timer. Which is very unwanted in a quiz.
I don't think there is any way to stop this behaviour... but I'll ask anyway.
If there is not, mind suggesting what should I do?
Never, ever rely on javascript (or any other client-side time) to calculate elapsed times for operations done between postbacks, or different pages.
If you always compare server dates, it will be hard for people to cheat:
first page request, store the server time
ping with javascript calls each N seconds, compare the 2 server times, and return the elapsed (just for show)
when the user submits the form, compare the 2 server times, calculate the elapsed time, and discard the ones which took too long (ie: possible cheaters)
Apparently the preview rendering differs from the posted rendering. This paragraph is here to make sure the next two lines show up as code.
// Preserve native alert() if you need it for something special
window.nativeAlert = window.alert;
window.alert = function(msg) {
// Do something with msg here. I always write mine to console.log,
// but then I have rarely found a use for a real modal dialog,
// and most can be handled by the browser (like window.onbeforeunload).
};
No, there is no way to prevent alert from stopping the single thread in JavaScript. Probably you can use some other way of user notification, for example a floating layer.
It's modal and stops execution. Consider an alternative which does not pause execution like a Lightbox technique.
I think the question asker is trying to prevent cheating. Since a user can type javascript: alert("paused"); into the address bar, or make a bookmarklet to do that, it's easy to pause the quiz and cheat.
The only thing I can think of is to use Date() to get the current time, and check it again when the timer fires. Then if the time difference is not reasonably close to the intended timer duration, show an admonishment and disqualify the answer to that question or let them flunk the quiz. There is no way to prevent the user from pausing your quiz, but it should be possible to catch them.
Of course with any cheat-proofing, you motivate people to become better cheaters. A person could change the system time on their PC, and fool the javascript Date() constructor which gets the time from the operating system.
You can use an interval to do a repeated clock comparison against a one second interval length. The interval handler can also update a time-remaining field on the user's display. Then the users can feel the pressure build as time runs out on their quiz. Fun times!
The feedback loop on SyaZ's question has clarified the issues at stake.
Here's an attempt to summarize the good answers so far:
Client scripts are by nature are easy to manipulate to cheat an online quiz. SEE #Filini 's Server-side approach
window.alert = function(msg) {} will overriding alert() and perhaps defeat the low hanging fruit of putting in the addressbar: javascript:alert('Pausing page so I can google the answer') or I'll use my Phone-A-Friend now. Courtesy of #eyelidlessness
If you must use a client-side approach, instead of using setTimeOut(), you could use a custom date-compare-based pause function like this (concept by #Mnebuerquo, code example by me (#micahwittman)):
Example:
var beginDate = new Date();
function myTimeout(milsecs){
do { curDate = new Date(); }
while((curDate-beginDate) < milsecs);
}
function putDownYourPencils(milsecs){
myTimeout(milsecs);
var seconds = milsecs / 1000;
alert('Your ' + seconds + ' seconds are up. Quiz is over.');
}
putDownYourPencils(3000);
Ultimately, you cannot trust user input. Without keeping track of the time elapsed on the server, there's just no guarantee the data hasn't been manipulated.
However, if you're confident your quiz-takers aren't JavaScript-savvy, and are merely relying on a "trick" they found somewhere, you could test for cheating (pausing) with the following code, which doesn't require modifying window.alert:
var timer = {
startDatetime: null,
startSec: 0,
variance: 1,
exitOnPause: true,
count: function (config) {
var that = this;
if (typeof config == "object" && typeof parseInt(config.seconds) == "number" && !isNaN(parseInt(config.seconds)))
{
if (typeof parseFloat(config.variance) == "number" && !isNaN(parseFloat(config.variance))) this.variance = config.variance;
if (typeof config.exitOnPause == "boolean") this.exitOnPause = config.exitOnPause;
if (config.seconds > 0)
{
if (!this.startSec) this.startSec = config.seconds;
if (!this.startDatetime) this.startDatetime = new Date();
var currentDatetime = new Date();
if (currentDatetime.getTime() - this.startDatetime.getTime() > (this.startSec - config.seconds) * this.variance * 1000)
{
if (typeof config.onPause == "function") config.onPause();
if (!this.exitOnPause)
{
this.startDatetime = new Date();
this.startSec = config.seconds--;
window.setTimeout(function () { that.count(config); }, 1000);
}
}
else
{
config.seconds--;
window.setTimeout(function () { that.count(config); }, 1000);
}
}
else
{
if (typeof config.onFinish == "function") config.onFinish();
}
}
}
};
This timer object has a single method, count(), which accepts an object as input. It expects a seconds property in the input object at minimum.
For some reason, window.setTimeout doesn't always work as expected. Sometimes, on my machine, window.setTimeout(x, 1000), which should execute the code after 1 second, took more than 2 seconds. So, in a case like this, you should allow a variance, so people who aren't cheating don't get flagged as cheaters. The variance defaults to 1, but it can be overridden in the input object. Here's an example of how to use this code, which allows 2.5 seconds of "wiggle room" for slow-pokes:
timer.count({
seconds: 10,
onPause: function () { alert("You cheated!"); window.location.replace("cheatersAreBad.html"); },
onFinish: function () { alert("Time's up!"); },
variance: 2.5
});
With a solution like this, you could use Ajax to tell a server-side script that the user has paused the timer or redirect the user to a page explaining they were caught cheating, for example. If, for some reason, you wanted to allow the user to continue taking the quiz after they've been caught cheating, you could set exitOnPause to false:
timer.count({
seconds: 10,
exitOnPause: false,
onPause: function () { recordCheaterViaAjax(); },
onFinish: function () { alert("Time's up!"); },
variance: 2.5
});
The server session could be set to expire at say 1 hour. The javascript could be used as only a display tool for the user to know how much time is left. If he decides to cheat by pausing the timer, then he might be suprised when posting his test that his session has timed out.