How do you debounce a function after the first call? - javascript

I am trying to make a function similar to debounce, except I want the debounced function to be called immediately after the first call and all calls after the first call within the debounce time limit to be debounced. For example, if the function is hooked up to a button, and you rapidly and repeatedly click it 10 times, the function gets called on the first press and gets called a second time after x milliseconds have passed since the tenth press.

Pure JavaScript processing:
Requiring a repeated call within the "debounce" period to be called in addition to the first call adds a complication that can be addressed using a timer in addition to timestamps to make immediate calls if possible.
However, because calls to a function may be delayed, it is not always possible to return a value from the function to the caller in real time.
The following concept code
calls the process "lock" in the sense of locking out calls in a predefined way.
returns undefined to all calls without implementing call backs to allow a caller to tell if its call was actioned or not, or to retrieve a return value;
When a call is made after a lockout period within which addition call attempts were made, the actual arguments used are to place a delayed call are those supplied by the most recent call attempt.
function CallLock( toCall, lockout) {
let argv;
let lastCall = 0;
let timer = 0;
function recall() {
timer = 0;
lastCall = Date.now();
toCall(...argv);
}
return function( ...args) {
let now = Date.now();
if(timer == 0) {
if( now >= lastCall+lockout) {
lastCall = now;
toCall( ...args);
}
else {
argv = args;
timer = setTimeout(recall, lastCall+lockout - now);
}
}
else {
argv = args; // use most recent arguments
}
}
}
// test CallLock
let start;
function demo( msg) {
console.log( "demo('%s') called. Time is %sms after start", msg, Date.now() - start);
}
let lockDemo = CallLock( demo, 1000); // 1 second lockout
start = Date.now();
lockDemo("call 1");
setTimeout( lockDemo, 200, "call 2");
setTimeout( lockDemo, 400, "call 3");
setTimeout( lockDemo, 1800, "call 4");
Test code uses a 1 second lockout time. Note that timer delays are inexact and Date.now() rounds to the nearest millisecond. The expected results are
call 1 is made synchronously and will show a start time of 0 or 1ms.
call 2 is never actioned - its arguments are not used.
call 3 is actioned, but delayed until shortly after the first call lockout
call 4 is actioned, but also delayed because the lockout period from when call 3 was actioned is still in effect.

Sounds like throttle. check this article for difference between throttling and debouncing. if throttle isn't what you need, then you should implement what you need from scratch (and adding more explanation for the details).
Edit:
So, Yes, It is not throttle; It is debounce with invocation on leading edge in lodash;
_.debounce(yourCallback, 100, {
'leading': true
})

I would suggest don't go for debounce. The older debouncing technique relies on setTimeout which is not perfect. Instead try to make use requestAnimationFrame which has in built support for the next trigger for Dom visual states change.

Related

How can I make a function that calls another function every 60 seconds?

I have this function:
isAuthenticated = (): boolean => {
xxx
};
I am using AngularJS and I would like to know how can I make a function such as
keepCheckingAuthentication()
That will call the is Authenticated() function every 60 seconds?
There’s a setInterval function that will call any code with given time interval in ms:
var intervalID = setInterval(function() { console.log('Works!') }, 1000);
Later you can cancel the timer using clearInterval(intervalID).
If isAuthenticated can be modified to actually make a request to call the server in order to check authentication, and can return a promise that resolves or rejects when this is done, then I would do something like
var keepCheckingAuthentication = function() {
return isAuthenticated().catch(angular.noop).then(function(isAuth) {
// Can do something different if isAuth == false
return $timeout(keepCheckingAuthentication, 60 * 1000);
});
});
Note the call to catch. This effectively converts any rejections to a success, so the next then callback runs in every case.
Using something like this rather than $setInterval means there will always be 60 seconds between responses from the server, rather than requests made. On a slower connection, or an overloaded server, this means there is a lower chance of adding to the overloaded connection or server, as you're sure the previous request has finished before sending off another one.
you can probably use $interval function (wrapper of window.setInterval() in AngularJS)?
The documentation of $interval function is here
In your case keepCheckingAuthentication() is the function and you can adjust the other parameters accoding to your needs? Does this help?
example:
$interval(myFunctionAtInterval, 5000) // Scheduled for every 5 seconds, for instance
funtion myFunctionAtInterval() {...}
I tend to avoid setInterval whenever I can, i.e. always (Paul Irish talks about this in this video).
What I do is wrap a setTimeout in a function that recursively calls itself (with a condition that allows me to easily stop, or to let the data decide when it's not needed anymore).
var condition = true;
$scope.count = 1;
function myFunc(){
$scope.count += 1;
}
var timer = function(){
myFunc();
if( condition ){
$timeout(timer, 1000);
}
};
timer();
I am using angular built in $timeout here as it's always suggested.
A demo
use $interval.this code will help you:
var callefunc=function() {
console.log('hey i am calle');
}
$scope.caller=function(){
var stop = $interval(callefunc, 1000);
}
here you can call a caller function when you want to start function callefunc on interval of 1 second .

How to stop the previous instances of the same function if it's called multiple times?

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();
}
}
}

Prevent and queue action (but only once globally) if previously called within X seconds

I always run into this problem and seem to implement a nasty looking solution.
It seems like a common design pattern to fire an action immediately, but not let that action queue up if clicked rapidly / delay firing if previously called within a timeframe. In my real world example, I have an AJAX call being made, so if I don't prevent repetitive actions the browser queues requests.
How would you implement this differently? What other options are there?
function myFunction() {
console.log("fired");
}
var timeout = null;
$("#foo").click(function() {
// if not previously clicked within 1 second, fire immediately
if (!timeout) {
myFunction();
timeout = setTimeout(function() {
timeout = null;
}, 1000);
} else {
// clicked again within 1s
clearTimeout(timeout); // clear it - we can't have multiple timeouts
timeout = setTimeout(function() {
myFunction();
timeout = null;
}, 1000);
};
});
With your current code, if you repeatedly click "#foo" at an interval slightly less than one second, say every 800ms, on first click it will fire the function immediately (obviously), but then it will fire the function exactly once more one second after the last click. That is, if you click ten times at 800ms intervals the function will fire once immediately and a second time approximately 8 seconds (800ms * 9 + 1000ms) after the first click.
I think you're better off removing the else case altogether, so that on click it will fire the function if it has not been called within the last second, otherwise it will do nothing with no attempt to queue another call up for later. Not only does that seem to me like a more logical way to operate, it halves the size of your function...
On the other hand, since you mentioned Ajax, rather than disabling the function based on a timer you may like to disable the function until the last Ajax request returns, i.e., use a flag similar to your timerid and reset it within an Ajax complete callback (noting that Ajax complete callbacks get called after success or failure of the request).
In the case of an auto-complete or auto-search function, where you want to send an Ajax request as the user types, you might want to remove the if case from your existing code and keep the else case, because for auto-complete you likely want to wait until after the user stops typing before sending the request - for that purpose I'd probably go with a shorter delay though, say 400 or 500ms.
Regarding general structure of the code, if I wanted a function to be fired a maximum of once per second I'd likely put that control into the function itself rather than in a click handler:
var myFunction = function() {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid=null; }, 1000);
// actual work of the function to be done here
console.log("myFunction fired");
};
}();
$("#foo").click(function() {
myFunction();
});
The immediately invoked anonymous function that I've added makes it uglier, but it keeps the timerid variable out of the global scope. If you don't like that obviously you could simply declare timerid in the same scope as myFunction() as you currently do.
This answer is getting kind of long, but if you have a lot of different functions that all need some kind of repeat control in them you could implement a single function to handle that part of it:
function limitRepeats(fn, delay) {
var timerid = null;
return function() {
if (timerid) return;
timerid = setTimeout(function(){ timerid = null; }, delay);
fn();
};
}
// myFunction1 can only be called once every 1000ms
var myFunction1 = limitRepeats(function() {
console.log("fired myFunction1()");
}, 1000);
// myFunction2 can only be called once every 3000ms
var myFunction2 = limitRepeats(function() {
console.log("fired myFunction2()");
}, 3000);
$("#foo").click(function() {
myFunction1();
myFunction2();
});

How to rate-limit ajax requests?

There are several divs and handler to send ajax requests when they are clicked. My problem is that i don't know how to force my handler not to exceed limit of 1 request per 30 seconds.
Appreciate your help!
The excellent Underscore.js has a throttle function. You pass in the handler that you want to throttle and get back a rate-limited version of the same function.
var throttled = _.throttle(someHandler, 100);
$(div).click(throttled);
http://documentcloud.github.com/underscore/#throttle
Here's a simplified version that I've used in my own code:
function throttle(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
if (!timeout) {
// the first time the event fires, we setup a timer, which
// is used as a guard to block subsequent calls; once the
// timer's handler fires, we reset it and create a new one
timeout = setTimeout(function() {
timeout = null;
func.apply(context, args);
}, wait);
}
}
}
A good way to test it is by firing off a bunch of scroll events and watching your handler log to the Firebug console:
document.addEventListener("scroll", throttle(function() {
console.log("test");
}, 2000), false);
Here's a version that limits click-events on divs to once every 30 seconds, as requested (requires jQuery):
$("div").click(throttle(function() {
// ajax here
}, 30000));
If you want to rate limit, then unfortunately the _.throttle method that underscore.js provides is not your solution. Throttle will simply ensure your method is never called more than X seconds, and therefore all subsequent function calls will be disregarded until that period has passed.
If you want to rate limit so that you never call your function more than X times per second, but don't lose those function calls altogether, then you need a wholly different solution.
I have written an underscore extension at https://gist.github.com/1084831
You can see a working example at http://jsbin.com/upadif/8/edit#preview
Create a boolean canFireRequest, or whatever, flag and set it to false after each ajax request. Then create a 30 second time span that sets it back to true; check the flag's value before each new request.
Here's a rough example:
if ($(this).data('canFireRequest')) {
// Ajax request goes here
$(this).data('canFireRequest', false);
}
setTimeout(function() {
$(this).data('canFireRequest', true)
}, 30000);

Delay function from running for n seconds then run it once. (2minute question)

TLDR I have a function that runs on the end of a pan in an openlayers map. Don't want it to fire continously.
I have a function that runs on the end of panning a map.
I want it so that it will not fire the function until say 3 secconds after the pan has finished. Although I don't want to queue up the function to fire 10 or so times like setTimeout is currently doing.
How can I delay a function from running for n seconds then run it only once no matter how many times it has been called?
map.events.register("moveend", map, function() {
setTimeout(SetLocation, 5000);
});
Moveend:
moveend - triggered after a drag, pan, or zoom completes
The code above even using setTimeout(func, delay); still fires multiple times when it runs. How can I prevent this?
Well, meeting your requirements, you could build a simple function wrapper:
var executeOnce = (function (fn, delay) {
var executed = false;
return function (/* args */) {
var args = arguments;
if (!executed) {
setTimeout(function () {
fn.apply(null, args); // preserve arguments
}, delay);
executed = true;
}
};
});
Usage examples:
With your code:
map.events.register("moveend", map, executeOnce(SetLocation, 5000));
Other usages:
var wrappedFn = executeOnce(function (a, b) {
alert(a + ' ' + b);
}, 3000);
wrappedFn('hello', 'world');
wrappedFn('foo', 'bar'); // this won't be executed...
The wrapped function will be delayed the specified amount of time and executed only once.
For UI delays I would recommend using 'clearTimeout' in conjunction with 'setTimeout'. A call to 'setTimeout' returns an ID that is usually ignored. If, however, you store the ID, next time you are about to call 'setTimeout' you can cancel the previous 'setTimeout' (as though you never called it).
What I assume is happening in your case is:
(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
setTimeout (2nd)
...
callback from 1st setTimeout is called
callback from 2nd setTimeout is called
...
If, however, you use clearTimeout, you'll have:
(mouse move triggers callback)
setTimeout (1st)
(mouse move triggers callback)
clearTimeout (1st)
setTimeout (2nd)
...
callback from last setTimeout is called
To update the JavaScript code you provided:
var delayedSetLocationId = -1;
...
map.events.register("moveend", map, function() {
if (delayedSetLocationId >= 0) {
clearTimeout(delayedSetLocationId);
}
delayedSetLocationId = setTimeout(SetLocation, 5000);
});
...
function SetLocation(...) {
delayedSetLocationId = -1; // setTimeout fired, we can't cancel it now :)
...
}
This is precisely what setTimeout is for. If setTimeout is calling the function 10 times, there's something wrong with your code, which you didn't post.
Also keep in mind that setTimeout will not halt the script.
I have actually written a small post about this. It is mush like what CMS has suggested.
The code snippet looks like this:
var delayonetimeaction = {
oneTimeActions: {},
/***
** Will wait the supplied "delay" until executing
** the supplied "action" (function).
** If called a second time before the with the
** same id, the first call will be ignored and
** instead the second will be used - that is the
** second action will be executed after the second
** supplied delay, not the first.
***/
bind: function (delay, id, action) {
// is there already a timer? clear if if there is
if (this.oneTimeActions[id]) clearTimeout(this.oneTimeActions[id]);
// set a new timer to execute delay milliseconds from last call
this.oneTimeActions[id] = setTimeout(function () {
action();
}, delay);
},
};
http://sds-digital.co.uk/post/2015/04/21/js-delayed-one-time-action.aspx

Categories