I saw in a tutorial a debounce function made for angular js. It works well (but I do not understand the code very well).
The questions are:
Can you explain how this debounce works?
How can I reset the debounce function after it has worked one time?
function debounce(callback, interval) {
var timeout = null;
return function() {
$timeout.cancel(timeout);
timeout = $timeout(callback, interval);
};
};
inputNgEl.bind('keydown keypress', debounce(function () {
el.toggleClass('has-error', formCtrl[inputName].$invalid);
el.toggleClass('has-success', formCtrl[inputName].$valid);
}, 1000));
Can you explain how this debounce works?
The debounce(callback, 1000) returns a new function. So this inputNgEl.bind('keydown keypress',debounce(callback, 1000)) attaches function returned from debounce as event handler inputNgEl.
When events happen the attached event handler function is called possibly many times in row which results in:
time (in 100ms): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
keepresses : ↓ ↓ ↓
time to fire : 10 9 8 10 9 8 7 6 5 4 3 2 1 ✦ 10 9
callback
(in 100ms)
So when the event handler (returned from debounce) is called timeout variable is null. Calling $timeout.cancel(null) does nothing. Next we schedule the callback function to be executed after 1000 ms using $timeout service call. The call returns a promise that will be resolved after the given timeout (1000ms) elapses.
Now on second call to event handler the timeout variable has a promise so we cancel it - the callback function wont be called. Next we immediately schedule callback function again to be executed after 1000 ms and store a new promise inside timeout variable.
How can I reset the debounce function after it has worked one time?
I'm not sure what you mean by reseting the function? Immediately after callback is called the debounce function state and behaviour is almost identical to initial state - except for timeout variable.
If you would like to remove the debounce effect after the the callback is called for the first time you could do something like:
function debounce(callback, interval) {
var timeout = null;
var inner = function() {
$timeout.cancel(timeout);
if(inner.interval){
timeout = $timeout(function(){
callback();
inner.interval = null;
}, interval);
} else {
callback();
}
};
inner.interval = interval;
return inner;
};
Which basically after first timeout elapses will switch to calling callback immediately.
Related
I am attempting to show 1 element at a time with a total of 3 elements. Each element fades in for 1 second, pauses for 2 seconds, then fades outs for 1 second. Thus, for all 3 elements, the total interval is 12 seconds (3 elements X 4 seconds each).
At first, everything appears to be working fine, but after a few intervals or so, the timing is inconsistent, and multiple elements will show at once.
Consequently, I have attempted to resolve my issue by searching for similar or equivalent questions to mine, and I have tried different methods (such as giving 1 second gap between element changes), but I get the same undesired results.
Any assistance is appreciated!
/**
* Stocks (Nasdaq, S&P 500, and Dow) —— Interval
* —————————————————————————————————————————————
*/
$(document).ready(function()
{
// Initialize stock HTML elements as variables
const nasdaq = $(".stocks .nasdaq");
const sp500 = $(".stocks .sp_500");
const dow = $(".stocks .dow");
// Hide all stock HTML elements upon DOM load (except Nasdaq)
nasdaq.show();
sp500.hide();
dow.hide();
// Run once before interval begins [00:00 —— 00:04]
nasdaq.delay(2000).fadeOut(1000);
// Run once before interval begins [00:04 —— 00:08]
sp500.delay(4000).fadeIn(1000).delay(2000).fadeOut(1000);
// Run once before interval begins [00:08 —— 00:12]
dow.delay(8000).fadeIn(1000).delay(2000).fadeOut(1000);
// Run interval for a every 12 seconds (4-second span per each stock)
setInterval(function() {
nasdaq.fadeIn(1000);
// Pause for 2 seconds before fading out
setTimeout(function() {
nasdaq.fadeOut(1000);
}, 3000);
// Pause for 2 seconds before fading in
setTimeout(function() {
sp500.fadeIn(1000);
}, 4000);
// Pause for 2 seconds before fading out
setTimeout(function() {
sp500.fadeOut(1000);
}, 6000);
// Pause for 2 seconds before fading in
setTimeout(function() {
dow.fadeIn(1000);
}, 8000);
// Pause for 2 seconds before fading out
setTimeout(function() {
dow.fadeOut(1000);
}, 10000);
}, 12000);
});
Additionally, I tried removing the setTimeout() function & used 3 setInterval() functions along with the delay() function that run at the same time, but I get the same undesired results.
// Run interval for a 4-second span every 12 seconds
setInterval(function() {
nasdaq.fadeIn(1000).delay(2000).fadeOut(1000);
}, 12000);
// Run interval for a 4-second span every 12 seconds
setInterval(function() {
sp500.delay(4000).fadeIn(1000).delay(2000).fadeOut(1000);
}, 12000);
// Run interval for a 4-second span every 12 seconds
setInterval(function() {
dow.delay(8000).fadeIn(1000).delay(2000).fadeOut(1000);
}, 12000);
UPDATE (CSS #keyframes solution): I know this may not be a solution for all situations that require an interval, but I created the desired results I'm seeking by using the #keyframes. Here's a CopePen example.
I have a function(testFunction) in my controller that gets called (by callFunction) a random number of times in a random space of time. So for example, callFunction could call testFunction 10 times; waits 30 seconds; and then calls testFunction 20 times; waits 24 hours and then calls the testFunction 2 times, etc.... I would like to execute another function 5 seconds after the last instance of that function call. For example:
vm.testFunction = () => {
console.log("a");
if(some conditional statement that determines that it's been five seconds since this function was last called)
{
console.log("b");
callTestFunction2();
}
}
A problem input:
callFunction() calls testFunction 2 times; waits 30 seconds; calls
testFunction 3 times; waits 2 seconds; calls testFunction 10 times:
The answer should be
aabaaaaaaaaaaaaab
It sounds like you'll want to use $interval or $timeout
$timeout docs and $interval docs
If you wanted to call callTestFunction2() after 5 seconds it would look like this
vm.testFunction = () => {
console.log("a");
$timeout(function() {
console.log("b");
callTestFunction2();
}, 5000); // Delay is in milliseconds 5 secs = 5000
}
I am creating a game that when your health goes below 0, on a keydown it prints in a div:
"Game_over_:(_click_to_continue" letter by letter. I was getting a bug where the letters would print out multiple times like ggggaaammmeee ooovvveerr.
I set up a conditional statement where if the state gameOver is 0 and after a keydown event, then it activates the setInterval, else it does not. That solved the problem to some extent, however, the state sometimes changes a second late, and after setting up console.log(this.state.gameOver), it sometimes prints 0 twice before this.state.gameOver actually changes to 1. Other times it works just fine.
if (this.state.gameOver===0){
console.log(this.state.gameOver)
var s1="Game_Over\n:(\nClick_to_continue..."
var v1 =0
var b = document.getElementById("redButton")
var arr1 = setInterval(function(){
if(v1===0){b.innerText=""};b.innerText= b.innerText+s1[v1];v1+=1;
if(v1==s1.length){clearInterval(arr1)}}, 100)
this.setState({gameOver:1})
}
It's still sometimes printing Ggaammee Ovveerr.
So all I had to do was set up a conditional statement that depends on whether a variable is true or false. Once the setInterval function is triggered, the variable changes to false so that more keydowns wont cause multiple calls to setInterval.
var check;
if (check!==false){
check=false;
var arr1 = setInterval(function(){
if(v1===0){b.innerText=""};
b.innerText= b.innerText+s1[v1];v1+=1;
if(v1==s1.length){clearInterval(arr1)}}, 100)}
Once I click the screen with the mouse:
check=true;
This seems pretty strange to me but I hope there's someone that has come across this before and point me in the right direction.
I have a variable being set in scope within an $interval and the value is sometimes "sticking" and sometimes not.
someInterval = $interval(function() {
SomeFactory.getsomething($scope.highestnumber)
.then(function(response) {
if (response.number > $scope.highestnumber)
$scope.highestnumber = response.number;
});
}, 30000);
Most of the time this works - $scope.highestnumber is set with the updated value and 30 seconds later getSomething() is called passing the updated value.
However, every now and then (and I wish I could duplicate on demand), $scope.highestnumber is set with a new value but getSomething() is called with the value it had before the update executed. I have verified it by catching the value of $scope.highestnumber after it's been set to the new value, and then checking what's being passed into getSomething() on the next cycle.
So the sequence would be...
$scope.highestnumber = 10
getsomething(10) called
returns response.number = 11
$scope.highestnumber set to 11 (verified)
getSomething(10) called 30 seconds later
Problem
Calling repeater('#myTable tr','Rows').count(); returns a Future, not an integer. I need to get the integer value so I can confirm that an additional row was added to a table.
Code
it('should add a new user when save button is clicked',function()
{
showModal();
//here I'm trynig to store the row count of my table into a local variable.
//a future is returned who's 'value' field is undefined.
var memberCount = repeater('#memberTable tr','Member Rows').count();
//this outputs 'undefined'
console.log(memberCount.value);
input('editedMember.name').enter('John');
input('editedMember.grade').enter(5);
input('editedMember.ladderPosition').enter(3);
element('#saveMemberButton').click();
sleep(1);
expect(element(modalId).css('display')).toBe('none');
//here is where I want to do the comparison against the above stored memberCount
expect(repeater('#memberTable tr', 'Member Rows').count()).toBe(memberCount.value + 1);
});
Test Result
Chrome 25.0 e2e should add a new user when save button is clicked FAILED
expect repeater 'Member Rows ( #memberTable tr )' count toBe null
/Users/jgordon/learning/chessClub/web-app/test/e2e/scenarios.js:45:3: expected null but was 6
Chrome 25.0: Executed 2 of 2 (1 FAILED) (1 min 4.117 secs / 1 min 3.773 secs)
Drilling into the source code for Angularjs' e2e support reveals that you have to call execute() on the Future to have it populate its value. Also, when you call execute you have to provide a "done" function to the execute() otherwise Testacular will (oddly enough!) skip your test.
Code
var rowCountFuture = repeater('#memberTable tr','Member Rows').count();
rowCountFuture.execute(function(){
});
var memberCount = rowCountFuture.value;
While I'm jazzed to see this works, I'm concerned there may be some asynchronous bugs that could come out of this, also, I feel like this is a hack and not the right way to do it. Any ideas?
Based on the latest Protractor version:
it('should add a new user when save button is clicked', function() {
var memberCount;
element.all(by.repeater('#memberTable tr','Member Rows')).count().then(function(value) {
memberCount = value;
});
...
// then do all your entering user info, saving etc.
...
browser.refresh(); // or however you want to load new data
expect(element.all(by.repeater('#memberTable tr','Member Rows')).count()).toEqual(memberCount + 1);
});
I've run into the same issue, and have seen confusing results when testing value returned after calling execute(). I've found this method works more reliably:
var getCount = repeater('ul li').count();
getCount.execute(function(value) {
expect(value).toEqual(3);
});
You can do this most easily in the async promise returned by the locator
element.all(By.repeater 'thing in things').then(function(elements){
count = elements.length;
expect(count).toEqual(3);
});