Debounce a function execution in a watcher (AngularJS) - javascript

I have a watch on vm.search variable, which is a ng-model of an input element. When I type something, I want to save it to database with a delayedSave function, but I don't want to save everything user types. So i decided to use lodash _.debounce function, but the problem is that this function doesn't work correclty within a $scope.$watch. delayedSave executes as many times as $scope.$watch function.
$scope.$watch('vm.search', nv => {
let savedQuery = _.find(vm.searchQueries, {query: nv});
if (savedQuery) {
vm.currentSearchQuery = savedQuery;
}
let runDebounce = _.debounce(delayedSave, 1000);
runDebounce(nv);
});
I could set debounce to $watch's callback, but I need to execute the code, I've written below, every time vm.seach has changed.
let savedQuery = _.find(vm.searchQueries, {query: nv});
if (savedQuery) {
vm.currentSearchQuery = savedQuery;
}

The same runDebounce method should be called repeatedly for the debounce to work. Since you are recreating the runDebounce function on each digest cycle, you're running a different method each time. Since the method is not called again, the debounce timeout passes, and the wrapped method delayedSaved is invoked.
Move the creation of the debounced function runDebounce out of the $watch callback:
const runDebounce = _.debounce(delayedSave, 1000);
$scope.$watch('vm.search', nv => {
const savedQuery = _.find(vm.searchQueries, {query: nv});
if (savedQuery) {
vm.currentSearchQuery = savedQuery;
}
runDebounce(nv);
});

Related

Javascript Debounce not clearing it's queue from vue component

I am using the debounce method from here https://www.freecodecamp.org/news/javascript-debounce-example/
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function saveInput(){
console.log('Saving data');
}
const processChange = debounce(() => saveInput());
and I want to include in a library we have, so in common.js I have:
export default {
otherStuff,
debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
},
in vue.js I have a textbox which has an event #keyup="searchboxChange()"
and in the methods section:
import common from "#/assets/js/Service/Common";
... stuff removed for brevity
methods:
{
searchboxChange(){
common.debounce(() => this.filterChanged(), 1000)();
},
}
I had to include () at the end of the debounce method else it didn't actually fire. However, while it debounces perfectly, when the timeout expires every event is then fired. So if my search was "HELLO" I would see 5 requests all fired at the same time as this.filterChanged() was called 5 times.
I am sure it is something simple with the scope of the timeout variable, because adding a console.log into the debounce method shows the timer is undefined each time.
You need to debounce the component method, otherwise you'll be invoking multiple debounced functions from within your component method.
Something like this should work
methods: {
// ...
searchboxChange: common.debounce(function() {
this.filterChanged();
}, 1000)
}
Notice the use of function as opposed to short function syntax. You'll need to do this to ensure the correct lexical scope of this
Firstly, as always, thanks to everyone who contributed a solution. However, none got past the "this" is not the right scope.
The solution was to set the function in created. (source: https://forum.vuejs.org/t/lodash-debounce-not-working-when-placed-inside-a-method/86334/4)
which (in case link goes dead) is effectively saying
move the part that you need to debounce into its own method and debounce that (like you did in the codepen for he first method).
Then you can call the debounced method from the event handler method.
It’s also better to debounce each instance method dynamically during created, otherwise component instances that the same debounced function and that can lead to wonky debounce behaviour:
and their code sample:
created() {
this.updateForm = _.debounce(this.updateForm, 500)
},
methods: {
triggerUpdate(event){
// perform some action before debouncing
this.updateForm(event)
} ,
updateForm: (event){
console.log('in update')
}
so for my case:
created() {
this.searchboxChange = common.debounce(this.filterChanged, 1000)
},
yes, that is literally it.
result:
only one network call now.

Implement debounce: how to make three invocations result in one effective call?

How can I invoke three times a function with a setTimeOut but just print it once after 100 milliseconds??
This is the definition of debounce that I have to implement:
Debounce ignores the calls made to it during the timer is running and
when the timer expires it calls the function with the last function
call arguments, so I want to achieve that with Javascript
A function will be debounced as follows:
receivedEvent = debounce(receivedEvent, 100)
My attempt:
function debounce(func, timeInterval) {
return (args) => {
setTimeout(func, timeInterval)
}
}
function receivedEvent() {
console.log('receive')
}
receivedEvent();
receivedEvent();
receivedEvent();
But this still generates 3 outputs. I need it to only produce one output according to the requirements.
In your attempt you did not call debounce, but just called your own function receivedEvent. Maybe the site where your attempt is tested will do this for you, but we cannot know this from your question. Just make sure it is called.
To test the requirements you need to use a better use case: one based on a function that receives arguments. This is needed because you must prove that the debounced function is called after the timeout with the last passed arguments.
The key to this pattern is to use variables within a closure:
function debounce(func, timeInterval) {
let timer;
let lastArgs;
return (...args) => {
lastArgs = args; // update so we remember last used args
if (timer) return; // not ready yet to call function...
timer = setTimeout(() => {
func(...lastArgs);
timer = 0; // reset timer (to allow more calls...)
}, timeInterval);
}
}
function receivedEvent(arg) {
console.log('receive ' + arg)
}
receivedEvent = debounce(receivedEvent, 100)
receivedEvent("a");
receivedEvent("b");
receivedEvent("c");
// Output will be "c" after 100ms
Note that the question's definition of "debounce" deviates a bit from its usual definition, where the first invocation actually calls the function immediately, and only then starts the timeout (cooldown-period).

arguments in function inside setInterval not changing

I'm implementing polling using setInterval but I'm running into some issues. I have a function to poll that looks like this
const pollMessages = () => {
if (interval) {
return false;
}
interval = setInterval(callApi, delay);
};
I also have a function to clear the interval
const stopPolling = () => {
if (interval) {
clearInterval(interval);
interval = null;
}
};
The callApi function takes in an argument and makes a request to an endpoint. It looks like this
const callApi = () => {
Api.get('/new-message', lastMessage)
}
The catch here is every time a request is made, if there are new messages, the lastMessage variable is supposed to be updated but the issue I'm having is that the value doesn't update in the new requests. It continues sending with the old lastMessage. I've tried passing the updated lastMessage as an argument to callApi but it doesn't make any difference. I'd want a situation where the lastMessage argument can be changed in some requests while setInterval is running. How do I resolve this?
If I am understanding your code correctly, this is because the value of lastMessage is being sent as a parameter, not a reference to the variable itself.
You'll want to create a setter function that you can call with a value to pass in order to change the variable.
For example:
const setLastMessage = (value) => {
lastMessage = value;
}
You'd then pass setLastMessage as a parameter and adjust your API functions accordingly.

Invoke and return a function

Is there a way to do this in JS
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
return function() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}.call(null)
// the .call does not work as intended here, but this is basically what I want to do.
}
I think I can do namedFunction()() with the code above in order to invoke both, but I'm wondering if there is another way.
The longer version of the function would look like this:
function namedFunction(elements,args) {
const domElements = document.querySelector(elements);
const initialValue = 0;
let incrementBy = 5;
function namedFunctionEventHandler() {
// Do something to domElements based on initialValue and incrementBy
// function needs to run the first time namedFunction is called
// and this is the only function that needs to run on subsequent calls to namedFunction
}
namedFunctionEventHandler();
return namedFunctionEventHandler;
}
The goal would be to pass a single function as an event handler, that the first time it runs it does initial calculations, caches dom elements and the more heavier stuff, then executes the logic that is abstracted in the returned function and on subsequent calls it uses the data from the closure.
Edit: the namedFunction does not need to accept any arguments, its just for demonstration purposes.
document.addEventListener('scroll', namedFunction)
is what I want to be able to do.
#CertainPerformance - Sorry, I misread your answer.
If you take a look at the end result I would like to achieve, your proposition wont actually work as intended, as if I pass an invoked function as an event handler, its gonna run before an event has actually happened.
You can make namedFunction into an IIFE that saves a reference to a function (initially undefined). On call, if that variable is undefined, carry out the expensive calculations and then assign to the variable; if the variable is defined, then simply call it.
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
const domElements = document.querySelector(elements);
...
cheapFn = () => {
// assign to cheapFn
};
cheapFn();
};
})();
Demo:
const handler = (() => {
let cheapFn;
return () => {
if (cheapFn) {
cheapFn();
return;
}
// expensive calculations here
console.log('expensive');
cheapFn = () => {
console.log('cheap');
};
cheapFn();
};
})();
document.addEventListener('scroll', handler);
body {
height: 400px;
}
body
You can take advantage of the fact that functions in JavaScript are first-class objects, and store the function state (initialized/uninitialized) in a property of the function.
The data computed during initialization can be stored in the function properties as well, please take a look at the demo:
const namedFunction = function(elements,args) {
if (!namedFunction.isInitialized) {
console.log('Initialization: Computing initial value...');
namedFunction.initialValue = 10 * 10;
console.log(`Initial value: ${namedFunction.initialValue}`);
namedFunction.isInitialized = true;
}
return function() {
console.log('Running regular operation:');
console.log(`Current value: ${--namedFunction.initialValue}`);
}.call(null)
}
document.getElementById('demo').addEventListener('click', namedFunction);
<button id="demo">Run</button>

How to write a function with an Observable?

I want to create a function that returns an Observable.The description of the function is as follows:
The function should be delayedAlert(message:string, time:number) that returns an Observable.
The function should contain setTimeout function inside delayedAlert which prints message after the set 'time' value.
Ex:
delayedAlert(message, time){
return new Observable//how to implement setTimeout function here?
Use Observable.create to create the observable, and in the first callback, write the logic to populate the observable, which in your case is the setTimeout.
function delayedAlert(msg, time) {
return Observable.create(
observer => setTimeout(() => observer.onNext(msg), time));
}
Then to use it:
delayedAlert("Hi, Sally", 1000).subscribe(msg => alert(msg));
However, if you are using observables, you don't need to use setTimeout; use delay instead, applied to of, which creates an observable from individual value(s):
function delayedAlert(msg, time) {
return Observable.of(msg).delay(time);
}
Since it's so easy to write it this way, you probably don't need the delayedAlert function at all:
const observable = Observable.of("Hi, Sally").delay(1000);
observable.subscribe(msg => alert(msg));

Categories

Resources