I'm trying to create a custom debounce function:
const debounced = [];
const cancelFunc = timeout => () => {
clearTimeout(timeout);
};
function debounce(fn, wait, ...args) {
let d = debounced.find(({ func }) => func === fn);
if (d) {
d.cancel();
} else {
d = {};
debounced.push(d);
}
d.func = fn;
d.timeout = setTimeout(fn, wait, ...args);
d.cancel = cancelFunc(d.timeout);
}
If I use with a named function, it works as intended:
debounce(foo, 1000); // called once with 5 clicks in 1 second
But I can't get it to work with anonymous functions:
debounce(() => { foo(5); }, 1000); // called 5 times with 5 clicks in 1 second
I created a pen here: https://codepen.io/anon/pen/gQvMdR?editors=1011
This happens because of your find condition. Let's back up, and consider this bit of code:
if (
(function(){ return 1 }) === (function(){ return 1 })
) {
console.log('The functions are equal');
} else {
console.log('The functions are NOT equal');
}
// logs 'The functions are NOT equal'
Even though I wrote two identical anonymous functions, they are not strictly equal to each other. When you pass in that anonymous function, that is essentially what you are doing. So, when you search for your array for a previously found function, it will never find a match, because each time debounce(() => { foo(5); }, 1000); is called it creates a new function. Since it'll never find a match, it will never be canceled.
As mentioned by #SLaks "Each call creates a separate function, so you won't find it in the array."
So you just need to store something in the array to match it to, you can use .toString()
// ================
const debounced = [];
const cancelFunc = timeout => () => {
clearTimeout(timeout);
};
function debounce(fn, wait, ...args) {
let d = debounced.find(({ funcString }) => funcString === fn.toString());
if (d) {
d.cancel();
} else {
d = {};
debounced.push(d);
}
d.func = fn;
d.funcString = fn.toString()
d.timeout = setTimeout(fn, wait, ...args);
d.cancel = cancelFunc(d.timeout);
}
// ================
function foo(value) {
console.log('value:', value)
}
function onClickBroken() {
debounce(() => { foo(5); }, 1000);
}
<button onClick="onClickBroken()">Click me 5 times</button>
Related
I'm learning decorators, and while I haven't been having good luck with tutorials, I tried to hack one myself and got this:
function sum(...args) { return [...args].reduce((sum,a)=> sum + a, 0) }
const decorate = (fn, callback) => (...args) => {
callback();
return fn(...args); // Return the result of the executed function
}
const running = () => console.log('Running')
const decoratedSum = decorate(sum, running)
console.log(decoratedSum(1,2,3)) // 'Running' // 6
I still had a tutorial post opened and instead of executing the function using f(arg) they use f.apply():
function sum(...args) { return [...args].reduce((sum,a)=> sum + a, 0) }
const wrap = (fn, callback) => function(){
callback();
return fn.apply(this, arguments);
}
const running = () => console.log('Running')
const wrappedSum = wrap(sum, running)
console.log(wrappedSum(1,2,3)) // 'Running' // 6
Is there a reason not to use decorate() over wrap()?
Also can someone please explain what's actually happening with fn.apply(this, arguements)?
I came across this code while learning about yield and wondering what yield would do as a argument to a function . Looks like a glorified return in this place
export function * throttle(func, time) {
let timerID = null;
function throttled(arg) {
clearTimeout(timerID);
timerID = setTimeout(func.bind(window, arg), time); // what does this do????
}
while(true) throttled(yield);
}
export class GeneratorThrottle {
constuctor() {};
start = () => {
thr = throttle(console.log, 3000);
thr.next('');
};
toString = () => {
console.log(throttle);
console.log('start =', this.start);
};
};
With next method, You can pass data as argument into generator function.
function* logGenerator() {
console.log(0);
console.log(1, yield);
console.log(2, yield);
console.log(3, yield);
}
var gen = logGenerator();
// the first call of next executes from the start of the function
// until the first yield statement
gen.next(); // 0
gen.next('pretzel'); // 1 pretzel
gen.next('california'); // 2 california
gen.next('mayonnaise'); // 3 mayonnaise
And func.bind(window, arg) are just fancy way to call it with argument,
where bind method return a function...
And you can access this as window in that function...
Example:
function func(args) {
console.log(this, args)
}
func.bind("this", "args")();
for example i have this program calling functions some with set time outs, some function work just calling them (setTimeout(this.siguienteNivel, 1500); but others needed (the last ones of the code) an arrow function, (​setTimeout(() =>​this.iluminarSecuenciaFinal();}, 1000 * i);) if the arrow function is missing the function does not trigger in these specific cases, i dont understand why this differentiator, the program is in a class, i dont know if that makes a difference
this is a piece, the complete code is this if needed:
https://github.com/moorooba/simon/blob/master/index.html
> elegirColor(ev) {
const nombreColor = ev.target.dataset.color;
const numeroColor = this.transformarColorANumero(nombreColor);
this.iluminarColor(nombreColor);
if (numeroColor === this.secuencia[this.subnivel]) {
this.subnivel++;
if (this.subnivel === this.nivel) {
this.nivel++;
this.eliminarEventosClick();
if (this.nivel === ULTIMO_NIVEL + 1) {
this.ganador();
} else {
setTimeout(this.siguienteNivel, 1500);
}
}
} else {
this.perdio();
}
}
// ganoElJuego() {
// swal("Platzi", "Felicitaciones, ganaste el juego!", "success").then(
// this.inicializar
// );
// }
// perdioElJuego() {
// swal("Platzi", "Lo lamentamos, perdiste :(", "error").then(() => {
// this.eliminarEventosClick();
// this.inicializar();
// });
// }
perdio() {
loser.classList.remove("hide");
setTimeout(this.startos, 1000);
this.eliminarEventosClick();
}
startos() {
loser.classList.add("hide");
boton.classList.remove("hide");
}
ganador() {
console.log("diparo");
this.eliminarEventosClick();
for (let i = 0; i < 4; i++) {
// setTimeout(this.iluminarSecuenciaFinal, 1000 * i);
setTimeout(() => {
this.iluminarSecuenciaFinal();
}, 1000 * i);
}
// setTimeout(this.ganoJuego, 4000);
setTimeout(() => {
this.ganoJuego();
}, 4000);
}
ganoJuego() {
console.log("ganojuego");
winner.classList.remove("hide");
// setTimeout(this.restart, 1500);
setTimeout(() => {
this.restart();
}, 1500);
}
You have to understand how this works in JavaScript:
class Foo {
bar = 'bar';
log() {
console.log(this.bar);
}
method() {
setTimeout(this.log);
setTimeout(() => this.log());
}
}
const obj = new Foo;
obj.method();
In this example, (which represents the relevant part of your code) the method function tries to call log in two different ways.
In the first one, it passes a reference to this.log as a parameter of setTimeout. The setTimeout function receives a function as a parameter and it has no idea of the context in which it has been declared, it's as if you extracted it from the class. In that case this is being evaluated when the function executes and it evaluates to window. The result is undefined because window.bar is undefined.
In the second one, it passes an arrow function whose particularity is to retain this when it is declared. When it executes, this refers to the instance of the class so the result is the same as if you called obj.log(). The result is 'bar' in that case.
I'm trying to wrap a list of functions over a callback function. Each of the function in the list takes in the value of the callback and returns a modified value. When I try to do this in a straight forward way, it does recursion, and the stack eventually runs out of space, and throws an error.
I tried solving the problem by using a wrapper function that took in a function, wrapped it with another one, and then returned it, and that solved the problem.
Look at the subscribe function:
class Observable {
constructor() {
this._subscribers = [];
this._operators = [];
}
next(val) {
this._subscribers.forEach(subscriber => {
subscriber(val);
});
}
subscribe(callback) {
if (this._operators.length > 0) {
let ogCallback;
this._operators.forEach((operator, index) => {
ogCallback = callback;
/** ==== call stack full error =====
* callback = (val) => {
* ogCallback(operator(val));
* };
*/
// This works
callback = ((func) => {
const wrapper = (val) => {
func(operator(val));
};
return wrapper;
})(ogCallback);
});
this._operators = [];
}
this._subscribers.push(callback);
}
pipe(operator) {
this._operators.unshift(operator);
return this;
}
}
const observable = new Observable();
observable.pipe(val => val + 2).pipe(val => val * 2).subscribe(val => console.log(val));
observable.next(5);
Why does this happen? They both seem to be the same thing.
I suspect it's from the series of closures created by:
ogCallback = callback;
callback = (val) => {
ogCallback(_function(val));
}
ogCallback and callback are global. After the initial iteration, callback has the value:
(val) => ogCallback(_function(val))
ogCallback has a closure to the global ogCallback, so it's value is whatever it was given from the last iteration, as does callback, potentially causing circular references.
The second example breaks the closure by creating a local variable func in the assigned function expression that is passed the value of ogCallback using an immediately invoked function expression (IIFE).
Original second example
_functions.forEach((_function, index) => {
ogCallback = callback;
callback = ((func) => {
const wrapper = (val) => {
func(_function(val));
};
return wrapper;
})(ogCallback);
});
Context: I was writing a simple throttle under the task of a javascript tutorial
Task: write a throttle that works like this:
function f(a) {
console.log(a)
};
// f1000 passes calls to f at maximum once per 1000 ms
let f1000 = throttle(f, 1000);
f1000(1); // shows 1
f1000(2); // (throttling, 1000ms not out yet)
f1000(3); // (throttling, 1000ms not out yet)
// when 1000 ms time out...
// ...outputs 3, intermediate value 2 was ignored
// P.S. Arguments and the context this passed to f1000 should be passed to the original f.
Here's my solution. Strangely, it works fine when I run it step-by-step in a debug console but not otherwise. Any idea why and how to fix it? (I assume it has to do with setTimeout?)
function throttle(f, ms) {
let isCoolDown = true,
queue = []
function wrapper(...args) {
queue.push(args)
if (!isCoolDown) return
isCoolDown = false
setTimeout(function() {
isCoolDown = true
if (queue[0] !== undefined) {
f.apply(this, queue.slice(-1))
queue = []
}
}, ms)
return function() {
f.apply(this, args)
queue = []
}()
}
return wrapper
}
A few things:
1) swap isCoolDown = false and isCoolDown = true
2) you don't need a queue, only one call has to go through the others get discarded with throttling
function throttle(fn, ms) {
let throttle = false;
let timer;
return wrapper(...args) {
if(!throttle) { // first call gets through
fn.apply(this, args);
throttle = true;
} else { // all the others get throttled
if(timer) clearTimeout(timer); // cancel #2
timer = setTimeout(() => {
fn.apply(this, args);
timer = throttle = false;
}, ms);
}
};
}
There is a bug at this line:
f.apply(this, queue.slice(-1))
The .slice method will return an array. Since the args is an array, the result of queue.slice(-1) would be something like:
[ [1, 2, 3] ]
Instead, you can change it to:
f.apply(this, queue.slice(-1)[0])