const asyncMethod = async () => {
const data = await fetchData()
return data.map(parseResponse)
})
const a = _.debounce(asyncMethod, 0, { leading: true })
Could you call a.cancel() and also have the async method stop?
Straight from the documentation:
Creates a debounced function that delays invoking func until after
wait milliseconds have elapsed since the last time the debounced
function was invoked. The debounced function comes with a cancel
method to cancel delayed func invocations and a flush method to
immediately invoke them. Provide options to indicate whether func
should be invoked on the leading and/or trailing edge of the wait
timeout. The func is invoked with the last arguments provided to the
debounced function. Subsequent calls to the debounced function return
the result of the last func invocation.
const fn = () => console.log('foo')
const dFn = _.debounce(fn, 500)
dFn()
dFn.cancel() // Remove this to see it working and leave it to cancel
Lets try with setTimeout:
const fn = () => setTimeout(function(){console.log('foo')}, 200)
const dFn = _.debounce(fn, 500)
dFn()
dFn.cancel() // Remove this to see it working and leave it to cancel
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Now if the async function got to the point of being executed there is nothing you can do really.
I had the same problem. My decision is to use AbortController. And invoke both .cancel() and .abort() methods.
See the documentation.
Related
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.
So I had this originally:
requestAnimationFrame(appendItemsFragment(itemsFragment, appendItemsFragmentCallback));
But I needed to do something more, so I transformed it to
requestAnimationFrame(() => {
appendItemsFragment(itemsFragment, appendItemsFragmentCallback);
myNewFunctioncall();
});
Original function is defined as:
const appendItemsFragment = (itemsFragment, callback) => () => {
itemsContent.appendChild(itemsFragment);
destroySpinner();
if (callback) {
callback();
}
};
But in the modified version of the requestAnimationFrame, the function appendItemsFragment is not executed (I put an alert() and it does not work, I press F10 two times and it comes back to the end of requestAnimationFrame). Why is that?
appendItemsFrament() returns a callback function. In the original code, requestAnimationFrame() executes that callback. Now you need to do it in your wrapper function.
requestAnimationFrame(() => {
appendItemsFragment(itemsFragment, appendItemsFragmentCallback)();
myNewFunctioncall();
});
However, this will call appendItemsFragment() for every frame that's displayed, which will repeatedly append the item fragment, which is probably not what you want. You need to call it once, save the result, and then call that in your wrapper function.
let callback = appendItemsFragment(itemsFragment, appendItemsFragmentCallback);
requestAnimationFrame(() => {
callback();
myNewFunctioncall();
});
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).
every time when i use input , my function send data to server and i get response, but if i want to write in field 'name' - Thomas Edison , i will send letter by letter
i try to put setTimeout function and if user still writing a string nothing will be send , but i does not work
#input="throttledSave"
throttledSave (e) {
let eva = e
let DELAY = 2000;
if(e.target.value){
return this.throttle(this.setDataFinalJSON, DELAY, eva);
}
},
throttle: function (callback, limit,eva) {
var wait = false;
var typingTimer;
return function (callback, limit,eva) {
clearTimeout(typingTimer)
if (!wait) {
callback(eva);
wait = true;
typingTimer = setTimeout(function () {
console.log('oh again')
wait = false;
}, limit);
}
}
}
every time it is work until DELAY , i don't know why, maybe clearTimeout does not work , i got stuck. I don't know why if i write some text so fast i got console.log('oh again')
You could do this with lodash debounce (https://lodash.com/docs/4.17.15#debounce) method:
Creates a debounced function that delays invoking func until after
wait milliseconds have elapsed since the last time the debounced
function was invoked. The debounced function comes with a cancel
method to cancel delayed func invocations and a flush method to
immediately invoke them. Provide options to indicate whether func
should be invoked on the leading and/or trailing edge of the wait
timeout. The func is invoked with the last arguments provided to the
debounced function. Subsequent calls to the debounced function return
the result of the last func invocation.
_.debounce(func, [wait=0], [options={}])
Example:
methods: {
throttledMethod: _.debounce(() => {
console.log('I only get fired once every two seconds, max!')
}, 2000)
}
Best to use the vue variant of lodash: https://www.npmjs.com/package/vue-lodash
Timeout just delays each input event (so that each one causes the request, just after some time) which is not what you want. The basic idea of implementing this is simple: store the time of the last input event in the model, and on input, send your requests only when timeout has passed, something like:
data () {
return {
...
lastInputTime: null,
inputTimeout: 1000 // ms
}
},
...
methods: {
throttledSave (e) {
const attemptTime = new Date();
if(this.lastInputTime && attemptTime - this.lastInputTime > this.inputTimeout) {
// get value, send request etc
}
this.lastInputTime = attemptTime;
}
Well, this is exactly what is called debounce, dreijntjens suggests a similar thing but using a library which allows to decorate your function.
PS Actually, such decorating is a better approach (unless you are planning to change inputTimeout in runtime) since you don't clutter your model with extra stuff specific to debouncing; you can make your own "decorator" (not in the strict sence, decorators are supposed to have special syntax, rather than being a function that gets your function and returns a modified one) if your project doesn't tree-shake libraries properly. Something like this:
function debounce(func, timeout) {
let lastTime = null;
return function() {
const attemptTime = new Date();
if(lastTime && attemptTime - lastTime > timeout) {
func.apply(this, arguments);
}
lastTime = attemptTime;
}
}
lodash's implementation is much more sophisticated since it supports several options.
How about using the lazy input model modifier?
VueJS prototype for delayed (lazy) input
Vue.prototype.lazyInput = function(e, delay) {
const self = this;
if (typeof delay == 'undefined') {
delay = 500;
}
const target = e.target;
if (self.lazyTimer) {
clearTimeout(self.lazyTimer);
self.lazyTimer = null;
}
self.lazyTimer = setTimeout(function(){
target.dispatchEvent(new Event('change'));
}, delay);
}
Usage:
<input v-model.lazy="{variableName}" #input="lazyInput($event)">
You can always use the native setTimeout()
methods: {
search: function (event) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
... XMLHttpRequest ...
}, 2000)
every 2000 msec sending request if no new data.
How do I block on the event?
const EventEmitter = require('events').EventEmitter;
const util = require('util');
function Task() {
EventEmitter.call(this);
this.setMaxListeners(Infinity);
this.dosomething = () => {
console.log("do something");
};
this.run = (iter) => {
for(i = 0; i < iter; i++) {
this.dosomething();
this.emit('someevent');
}
}
}
util.inherits(Task, EventEmitter);
task = new Task();
function do_work() {
console.log("work 1");
task.once('someevent', (props) => {
console.log('event happened');
});
console.log("work 2");
}
do_work();
task.run(5);
Acutal Result
work 1
work 2
do something
event happened
do something
do something
do something
do something
Expected Result
work 1
do something
event happened
work 2
do something
do something
do something
do something
If I understand your question correctly, then this can be achieved via a Promise that wraps the task event handler:
async function do_work() {
console.log("work 1");
// use a promise to block completion of do_work() function
// until task event callback has been invoked
await (new Promise(resolve => {
task.once('someevent', (props) => {
console.log('event happened');
// Now that task callback has been invoked, "resolve" the
// enclosing promise to allow do_work()'s execution to complete
resolve();
});
}));
console.log("work 2");
}
The idea in the code above is to wrap the task someevent handler so that the promise resolve can be invoked once the event handler has fired (ie by calling resolve()). This allows the call to do_work() to resume, to achieve the desired execution behavior.
Also, you will need to do something like this:
// Because do_work() is async, and becase you want to ensure that
// do_work() completes before starting task.run(5), you need to enclose
// these in an async function, eg doWorkThenRunTasks()
async function doWorkThenRunTasks() {
// Call do_work asynchronously
await do_work()
// task.run will happen after do_work completed
task.run(5);
}
doWorkThenRunTasks();
The addition of the async doWorkThenRunTasks() function allows you to use await in relation to do_work() to enforce execution order of task.run(5) after completion of do_work().
Hope this helps!