I found a great debounce()
function code online, but I am having a hard time converting it from ES5 to ES6.
The issue is as follows: When I use the ES5 implementation, everything works perfectly. The window is resized, console.log()
is immediately triggered, and subsequent resizings are ignored until after the 500ms I specified.
However, in the ES6 implementation, the first call works immediately... but every time after that it is also delayed by 500ms, even after cooling down!
If there is anyone who knows what I am doing wrong, I would really appreciate some help.
Examples:
function debounceES5(func, wait, immediate) {
var timeout;
return function () {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const debounceES6 = (callback, wait, immediate=false) => {
let timeout = null
return (...args) => {
const callNow = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) { next() }
}
}
document.querySelector('.es5').addEventListener('click', debounceES5(() => {console.log('clicked')}, 1000, true))
document.querySelector('.es6').addEventListener('click', debounceES6(() => {console.log('clicked')}, 1000, true))
Click both of these buttons fast and see how they react differently
<br />
<button class="es5">ES5</button>
<button class="es6">ES6</button>
Debouncing is a programming pattern or a technique to restrict the calling of a time-consuming function frequently, by delaying the execution of the function until a specified time to avoid unnecessary CPU cycles, and API calls and improve performance.
The debounce() function forces a function to wait a certain amount of time before running again. The function is built to limit the number of times a function is called. The Send Request() function is debounced. Requests are sent only after fixed time intervals regardless of how many times the user presses the button.
Here's your ES5 function written in ES6 - without skipping ANY details (except the context
which is not relevant)
const debounce = (func, wait, immediate=false) => {
let timeout;
return (...args) => {
const later = () => {
timeout = null; // added this to set same behaviour as ES5
if (!immediate) func(...args); // this is called conditionally, just like in the ES5 version
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func(...args);
};
};
Note, the later
function does exactly what the ES5 version does
This should now behave (almost) identically to the ES5 version ... the whole context = this
thing seems completely weird in the ES5 version anyway makes sense with example usage
However, as per comments, since the code is used for event handler,
this
is quite important, it's the Event Target so, you really can't return anarrow function
BEtter code would be
const debounce = (func, wait, immediate=false) => {
let timeout;
return function (...args) {
const later = () => {
timeout = null; // added this to set same behaviour as ES5
if (!immediate) func.apply(this, args); // this is called conditionally, just like in the ES5 version
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
};
You can't use arrow functions in debounce
(well, you need to know where you can and where you can't)
Arrow function bind this
when they are created. That means the this
in an array will never and can never change.
For example
'use strict';
function safeToString(v) {
return v === undefined
? 'undefined'
: v.toString();
}
function regularFunc() {
console.log('regular:', safeToString(this));
}
const arrowFunc = () => {
console.log('arrow:', safeToString(this));
};
regularFunc();
arrowFunc();
regularFunc.call("hello");
arrowFunc.call("world");
Notice in the regularFunc
case this
is undefined and later we can redefine it to hello
but in the arrowFunc
case it's always [Object Window]
So no to your ES6 debounce. Here's The supposedly working ES6 version
const debounce = (callback, wait, immediate) => {
let timeout;
return (...args) => {
const callNow = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) { next() }
}
}
Let's test it
function es5Debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const es6Debounce = (callback, wait, immediate) => {
let timeout;
return (...args) => {
const callNow = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) {
next()
}
}
}
function safeToString(v) {
return v === undefined
? 'undefined'
: v.toString();
}
class Test {
constructor(name) {
this.name = name;
}
log(...args) {
console.log(
this ? this.name : 'undefined',
safeToString(this),
...args);
}
}
class ES5Test extends Test {
constructor() {
super('ES5:');
}
}
ES5Test.prototype.log = es5Debounce(ES5Test.prototype.log, 1);
class ES6Test extends Test {
constructor() {
super('ES6:');
}
}
ES6Test.prototype.log = es6Debounce(ES6Test.prototype.log, 1);
const es5Test = new ES5Test();
const es6Test = new ES6Test();
es5Test.log("hello");
es6Test.log("world");
As you can see the es6 version fails because this
is wrong. If you only ever use non-member functions then es6Debounce
will look like it's working but as soon as you use member functions on a class or event handlers you'll see es6Debounce
does not work, this
is not set correctly.
The code here tries to show the error. ES5Class
and ES6Class
are identical. The test should print
ES5: [object Object] hello
ES6: [object Object] world
instead it prints
ES5: [object Object] hello
undefined undefined world
As another example let's try an event handler
function es5Debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this,
args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
const es6Debounce = (callback, wait, immediate) => {
let timeout;
return (...args) => {
const callNow = immediate && !timeout
const next = () => callback(...args)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) {
next()
}
}
}
function mousemove(e) {
console.log(this.id, e.pageX, e.pageY);
}
document.querySelector('#es5')
.addEventListener('mousemove', es5Debounce(mousemove));
document.querySelector('#es6')
.addEventListener('mousemove', es6Debounce(mousemove));
#es5, #es6 {
margin: 1em;
width: 10em;
height: 2em;
display: inline-block;
}
#es5 {
background: orange;
}
#es6 {
background: yellow;
}
<div id="es5">es5</div>
<div id="es6">es6</div>
Move the mouse over the 2 areas. Notice again the es6 one is wrong.
Whether or not that's important do you I have no idea but the original debounce
you posted explicitly has code to make that case work.
My preferred solution is the following one taken from Chris Boakes' blog post Understanding how a JavaScript ES6 debounce function works:
function debounce(callback, wait) {
let timeout;
return (...args) => {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => callback.apply(context, args), wait);
};
}
Although it does not offer immediate
arg. is still good enough for it to be shared here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With