I several divs on my site, I'd like to update one by one. In order not to spam the server with 200+ requests at once, I'd like to delay them by 1s each.
What I tried:
var $tourBox = $('.tour-box');
$tourBox.each(function () {
var $box = $(this);
setTimeout(function () {
getUpdate($box);
}, 1000);
});
The update function:
function getUpdate($box) {
var $so = $box.attr('data-so');
var $url = $('#ajax-route-object-status').val();
$.get($url, {
so: $so
}).success(function (data) {
var $bg = 'bg-gray';
if (data.extra.isStarted === true && data.extra.isFinished === false) {
$bg = 'bg-orange'
}
if (data.extra.isStarted === true && data.extra.isFinished === true) {
$bg = 'bg-green'
}
if (data.extra.isLate === true && data.extra.isFinished === false) {
$bg = 'bg-red'
}
$box.removeClass('bg-gray').removeClass('bg-green').removeClass('bg-orange').removeClass('bg-red').addClass($bg);
});
}
In Chrome Dev--> Network it shows the all loaded as pending and then loads them one by one, but w/out the delay:
As you can see between 3907 and 3940, there is merely half a second delay. This doesn't change even if I have a timeout of 5000.
Back in 2008 I wrote a slowEach() plugin for jQuery that does what you're looking for. It's basically a drop-in replacement for $.each() and $(...).each() that takes a time interval, so the callback is called with that amount of delay for each element:
jQuery.slowEach = function( array, interval, callback ) {
if( ! array.length ) return;
var i = 0;
next();
function next() {
if( callback.call( array[i], i, array[i] ) !== false ) {
if( ++i < array.length ) {
setTimeout( next, interval );
}
}
}
};
jQuery.fn.slowEach = function( interval, callback ) {
jQuery.slowEach( this, interval, callback );
};
With that code, you can then do:
$('.tour-box').slowEach( 1000, function() {
getUpdate( $(this) );
});
One thing to note about this code is that it uses only a single timer at a time, instead of making hundreds of setTimeout() calls to fire up multiple timers at once. This makes it much easier on system resources.
Your for each is calling all of the timeouts at once. It is not waiting one second in between calling each time out. So you have thousands of objects that are being scheduled to call getUpdate($box); after one second.
What you can do is increase the timeout in each iteration.
var $tourBox = $('.tour-box');
var delay = 1000;
$tourBox.each(function () {
var $box = $(this);
setTimeout(function () {
getUpdate($box);
}, delay);
delay += 1000;
});
This will cause your first timeout to be fired after 1 second, and your second after two seconds and so on.
Calling one by one could be another solution to avoid spam like request.
In that case, this could be a solution:
$(document).ready(function(){
var $tourBox = $('.tour-box'),
curIndex = 0,
totalBox = $tourBox.length,
url = $('#ajax-route-object-status').val(),
function getUpdate(){
var $box = $tourBox.get( curIndex );
if ( typeof $box === 'undefined'){
// No more box to process
return; // exit
}
var $so = $box.attr('data-so');
$.get($url, {
so: $so
}).success(function (data) {
var $bg = 'bg-gray';
if (data.extra.isStarted === true && data.extra.isFinished === false) {
$bg = 'bg-orange'
}
if (data.extra.isStarted === true && data.extra.isFinished === true) {
$bg = 'bg-green'
}
if (data.extra.isLate === true && data.extra.isFinished === false) {
$bg = 'bg-red'
}
$box.removeClass('bg-gray').removeClass('bg-green').removeClass('bg-orange').removeClass('bg-red').addClass($bg);
}).always(function(){
// Increment index to process
curIndex++;
// Finished either with success or failed
// Proceed with next
getUpdate();
});
}
});
Related
I have a script that makes an ajax GET request once the user gets near the bottom of the page.
$(function(){
window.addEventListener('scroll', fetchImages);
window.addEventListener('scroll', fetchNotifications);
});
function fetchImages() {
var imagePage = $('.endless-pagination').data('next-page');
if(imagePage !== null) {
var last = $('.endless-pagination').data('last-item');
var within = $('.endless-pagination').data('within');
var orderBy = $('.endless-pagination').data('order-by');
clearTimeout( $.data( this, "scrollCheckImages" ) );
$.data( this, "scrollCheckImages", setTimeout(function() {
var scroll_position_for_images_load = $(window).height() + $(window).scrollTop() + 900;
if(scroll_position_for_images_load >= $(document).height()) {
$(".dual-ring-container").show();
$.ajax({
url: imagePage,
method: 'get',
cache: false,
})
.done(function( data ) {
if (last != null) {
$(".dual-ring-container").hide();
var newPageUrl = data.next_page + "&within=" + within + "&orderBy=" + orderBy + "&last=" + last;
$('.endless-pagination').append(data.images);
$('.endless-pagination').data('next-page', newPageUrl);
setResizeVariables();
updateReadMore();
initMentions();
}
});
}
}, 350))
}
}
However, there's a bit of a problem. This get request is essentially being spammed since the function is being triggered by a "scroll" event listener.
How could I make it so that this request can only be called once every few seconds or so?
Use a variable that keeps track of the last time the main body of the function was run, and return early if it was run less than a few seconds ago:
let lastCall = 0;
function fetchImages() {
const now = Date.now();
if (now - lastCall < 3000) return;
lastCall = now;
// ...
Note that you can use a similar strategy to reduce unnecessary indentation hell. Rather than
if (imagePage !== null) {
// big block of code
}
} // end of fetchImages
you can use
if (imagePage === null) return;
which will have the exact same effect, but will make your code a bit more readable.
I have a custom animating effect task in jQuery queue. And there is a setInterval call inside it.
After some time the stop() function is being invoked. It removes the callback of currently executing task from the queue and starts executing the next one.
But setInterval from the previous effect (which already having been removed) is still running. Where should I place the clearInterval to be invoked after cancelling the task with calling the stop()?
Here is an example:
$('body')
.queue(function(next) {
var i = 0, el = this;
var interval = setInterval(function() {
el.style.backgroundColor = i++ % 2 == 0 ? '#500' : '#050';
if (i > 5) {
clearInterval(interval);
next();
}
}, 1000);
})
.queue(function() {
this.style.backgroundColor = '#005';
});
setTimeout(function() {
$('body').stop();
}, 1500);
https://jsfiddle.net/coderlex/tLd9xtjj/
Move your interval variable instantiation outside of the queue closure function, then you can clear it whenever you call stop().
var interval = null;
$('body')
.queue(function(next) {
var i = 0, el = this;
interval = setInterval(function() {
el.style.backgroundColor = i++ % 2 == 0 ? '#500' : '#050';
if (i > 5) {
clearInterval(interval);
interval = null;
next();
}
}, 1000);
})
.queue(function() {
this.style.backgroundColor = '#005';
});
setTimeout(function() {
$('body').stop();
if (interval != null) {
clearInterval(interval);
interval = null;
}
}, 1500);
Not sure about official support of this method, but after reading the jQuery sources it seems I've found the solution. There is an undocumented second argument given to the callback function of the queue task. That's the object of the current effect's hooks. The property we need named stop accordingly. If set, the closure is called only in case of manual effect stopping by stop() or finish() methods. It's not being called on clearing or setting new queue.
Here is an example:
$('body')
.queue(function(next, hooks) {
var i = 0, el = this;
var interval = setInterval(function() {
el.style.backgroundColor = i++ % 2 == 0 ? '#500' : '#050';
if (i > 5) {
clearInterval(interval);
next();
}
}, 1000);
hooks.stop = function() {
clearInterval(interval);
}
})
.queue(function(next) {
this.style.backgroundColor = '#005';
next();
});
setTimeout(function() {
$('body').stop();
}, 1500);
I have several functions that use this given for loop below.
function startClaw(dir){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
isAnimating = $("#claw").is(':animated');
if(!isAnimating){// prevents multiple clicks during animation
if(isMoving || isDropping){ return; }
MCI = setInterval(function(){ moveClaw(dir); },10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
}
}
}
//.................................................................
function dropClaw(){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
}
}
Everything in the else if statement is different within the various functions. I'm wondering if there is any way to place the "pieces" of the for loop on the outside of the else if into its very own function. I feel like I've seen this or had done this a very long time ago, but it escapes me and I couldn't find any examples. Thanks everyone!
Previewing, I see this is similar to the above. Two differences (it looks like) are here the count gets passed to the function in case they needed to ever have different checks in the if statement, and, it's checking what the return value is since it looks like you return out of the loop if the condition is met. There are notes in comments in the code below.
function startClaw(dir) {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
isAnimating = $("#claw").is(':animated');
if (!isAnimating) { // prevents multiple clicks during animation
if (isMoving || isDropping) {
return true;
}
MCI = setInterval(function() { moveClaw(dir); }, 10);
//console.log("startClaw:" + dir);
stopSwingClaw();
}
return false;
});
}
//.................................................................
function dropClaw() {
// Pass a function as a callback to the method which expects to receive the count as a param
doReadCount(qdata, function(theCount) {
if (theCount === 5) {
if (isDropping) {
return;
} //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
});
}
function doReadCount(qdata, elseFunction) {
var readCount = 0;
var elseReturn;
for (var isRead in qdata) {
readCount++;
if (qdata[isRead]['reading'] == true) {
return;
} else {
// call the function that was sent and pass it the current read count. If the return is true, then also return true here
elseReturn = elseFunction(readCount);
if (elseReturn) {
return;
}
}
}
}
You can pass a function into another function to achieve this. I've done it for dropClaw, and it should be clear from my example how to do also extract startClaw.
function operateClaw(func){
var readCount = 0;
for(var isRead in qdata){
readCount++;
if(qdata[isRead]['reading'] == true){
return;
}else if(readCount == 5){
func();
}
}
}
function drop () {
if(isDropping){ return; } //prevent multiple clicks
stopSwingClaw();
isDropping = true;
MCI = setInterval(moveDown,20); //start heartbeat
}
function dropClaw () {
operateClaw(drop);
}
I am using google maps and i am trying to put a pause in execution to prevent QUERY_LIMIT usage issue. My function that plots the addresses looks like this.
The code works, however i want to try setTimeout or setInterval to see if its going to look better on UI.
How do i call it, what should be the first argument?
Thanx alot.
vLocations = [];
for (var i = 0; i < vAddresses.length; i++) {
//pause to prevent OVER_QUERY_LIMIT issue
//geocode "free" usage limit is 5 requests per second
//setTimeout(PlotAddressesAsUnAssigned, 1000);
//sleep(500);
//this will resolve the address and store it in vLocations
AddWaypointAndUnassigned(vAddresses[i]);
var z = i % 4;
if (z==0 && i != 0) {
//sleep after every 5th geocode call
//alert('going to sleep...i: ' + i);
//sleep(3000);
}
}
Doing a pause (asynchronous execution) inside a loop (synchronous) will usually result in a lot of trouble.
You can use recursive calls that are done only when a timeout ends.
var vLocations = [];
// Manages the timeout and recursive calls
function AddWaypointAndUnassignedWithPause(index){
setTimeout(function(){
// When the timeout expires, we process the data, and start the next timeout
AddWaypointAndUnassigned(vAddresses[index]);
// Some other code you want to execute
var z = i % 4;
if (z==0 && i != 0) {
//sleep after every 5th geocode call
//alert('going to sleep...i: ' + i);
//sleep(3000);
}
if(index < vAddresses.length-1)
AddWaypointAndUnassignedWithPause(++index);
}, 1000);
}
// Start the loop
AddWaypointAndUnassignedWithPause(0);
JSFiddle example.
Try this, hope this will help
vLocations = [];
for (var i = 0; i < vAddresses.length; i++) {
//pause to prevent OVER_QUERY_LIMIT issue
setTimeout(function(){
//this will resolve the address and store it in vLocations
AddWaypointAndUnassigned(vAddresses[i]);
}, 500);
var z = i % 4;
if (z==0 && i != 0) {
//sleep after every 5th geocode call
//alert('going to sleep...i: ' + i);
//sleep(3000);
}
}
What about a waiting line, thats fired when an item is added and stopped when there are no items left.
With setTimeout:
var INTERVAL = 1000 / 5;
var to = null;
var vLocations = [];
function addAddress(vAddress) {
vLocations.push(vAddress);
startTimeout();
}
function startTimeout() {
if( to === null ) {
to = setTimout(processLocation, INTERVAL);
}
}
function processLocation() {
if( vLocations.length ) {
var vAddress = vLocations.shift();
AddWaypointAndUnassigned(vAddress);
to = setTimout(processLocation, INTERVAL);
} else {
to = null;
}
}
With setInterval:
var INTERVAL = 1000 / 5;
var to = null;
var vLocations = [];
function addAddress(vAddress) {
vLocations.push(vAddress);
startInterval();
}
function startInterval() {
if( to === null ) {
to = setInterval(processLocation, INTERVAL);
}
}
function processLocation(cb) {
if( vLocations.length ) {
var vAddress = vLocations.shift();
AddWaypointAndUnassigned(vAddress);
} else
clearInterval(to);
to = null;
}
}
How would I have the h1 change for each iteration of the loop? This code now only displays the h1 text after everything is done.
for (i=0; i<array.length; i++) {
$("body > h1").text("Processing #" + i);
// things that take a while to do
}
Additional info: if I resize the window as it loops, the html updates.
var array = ['one', 'two', 'three']
var i = 0;
var refreshIntervalId = setInterval(function() {
length = array.length;
if (i < (array.length +1)) {
$("h1").text("Processing #" + i);
} else {
clearInterval(refreshIntervalId);
}
i++
}, 1000);
http://jsfiddle.net/3fj9E/
Use a setInterval with a one-millisecond delay:
var i=0, j=array.length;
var iv = setInterval(function() {
$("h1").text("Processing #" + i);
// things that take a while to do
if (++i>=j) clearInterval(iv);
}, 1);
http://jsfiddle.net/mblase75/sP9p7/
Sometimes you can force a render by forcing a recalculation of layout
for (i=0; i<array.length; i++) {
$("body > h1").text("Processing #" + i)
.width(); // force browser to recalculate layout
// things that take a while to do
}
It might not work in all browsers.
A better way, that does not block the browser so much:
function doThings(array) {
var queueWork,
i = -1,
work = function () {
// do work for array[i]
// ...
queueWork();
};
queueWork = function () {
if (++i < array.length) {
$("body > h1").text("Processing #" + i);
setTimeout(work, 0); // yield to browser
}
};
}
doThings(yourArray);
DEMO
I've spent a bit of time working out a jquery function that seems to solve this. Basically, it's a process handler that you can add any number of processes to and then call run to sequentially call these in a asynchronous way.
$.fn.LongProcess = function () {
var _this = this;
this.notifications = [];
this.actions = [];
this.add = function (_notification, _action) {
this.notifications.push(_notification);
this.actions.push(_action);
};
this.run = function () {
if (!_this.actions && !_this.notifications) {
return "Empty";
}
//******************************************************************
//This section makes the actions lag one step behind the notifications.
var notification = null;
if (_this.notifications.length > 0) notification = _this.notifications.shift();
var action = null;
if ((_this.actions.length >= _this.notifications.length + 2) || (_this.actions.length > 0 && _this.notifications.length == 0))
action = _this.actions.shift();
//****************************************************************
if (!action && !notification) {
return "Completed";
}
if (action) action();
if (notification) notification();
setTimeout(_this.run, 1000);
//setTimeout(_this.run,1); //set to 1 after you've entered your actual long running process. The 1000 is there to just show the delay.
}
return this;
};
How to use with <h1 class="processStatus"></h1>:
$(function () {
var process = $().LongProcess();
//process.add(notification function, action function);
process.add(function () {
$(".processStatus").text("process1");
}, function () {
//..long process stuff
alert("long process 1");
});
process.add(function () {
$(".processStatus").text("process2");
}, function () {
//..long process stuff
alert("long process 2");
});
process.add(function () {
$(".processStatus").text("process3");
}, function () {
//..long process stuff
alert("long process 3");
});
process.run();
});
if the process is very long you can use this script which shows every notification for a specific time interval.
here is the code..
html
<div id="ccNotificationBox"></div>
css
#ccNotificationBox{
-webkit-animation-name:;
-webkit-animation-duration:2s;/*Notification duration*/
box-sizing:border-box;
border-radius:16px;
padding:16px;
background-color:rgba(0,0,0,0.7);
top:-100%;
right:16px;
position:fixed;
color:#fff;
}
#ccNotificationBox.active{
-webkit-animation-name:note;
top:16px;
}
#-webkit-keyframes note{
0% {opacity:0;}
20% {opacity:1;}
80% {opacity:1;}
100% {opacity:0;}
}
javascript
var coccoNotification=(function(){
var
nA=[],
nB,
rdy=true;
function nP(a){
nA.push(a);
!rdy||(nR(),rdy=false);
}
function nR(){
nB.innerHTML=nA[0];console.log(nA[0]);
nB.offsetWidth=nB.offsetWidth;//reflow ios
nB.classList.add('active');
}
function nC(){
nB.classList.remove('active');
nB.innerHTML='';
nA.shift();
nA.length>0?nR():(rdy=true);
}
function init(){
nB=document.getElementById('ccNotificationBox');
nB.addEventListener('webkitAnimationEnd',nC,false);
window.removeEventListener('load',init,false);
}
window.addEventListener('load',init,false);
return nP
})();
usage
coccoNotification('notification 1');
example
http://jsfiddle.net/f6dkE/1/
info
the example above is perfect for external js as you use just one global variable which is the name of the function ... in my case coccoNotification
here is a different approach but it does the same
http://jsfiddle.net/ZXL4q/11/