Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorators and forwarding, call/apply #205

Merged
merged 11 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1 @@
The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call.
El contenedor devuelto por `spy(f)` debe almacenar todos los argumentos y luego usar `f.apply` para reenviar la llamada.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ importance: 5

---

# Spy decorator
# Decorador espía

Create a decorator `spy(func)` that should return a wrapper that saves all calls to function in its `calls` property.
Cree un decorador `spy(func)` que debería devolver un contenedor que guarde todas las llamadas para que funcionen en su propiedad `calls`

Every call is saved as an array of arguments.
Cada llamada se guarda como un array de argumentos.

For instance:
Por ejemplo

```js
function work(a, b) {
alert( a + b ); // work is an arbitrary function or method
alert( a + b ); // work es una función o método arbitrario
}

*!*
Expand All @@ -27,4 +27,4 @@ for (let args of work.calls) {
}
```

P.S. That decorator is sometimes useful for unit-testing. Its advanced form is `sinon.spy` in [Sinon.JS](http://sinonjs.org/) library.
P.D Ese decorador a veces es útil para pruebas unitarias. Su forma avanzada es `sinon.spy` en la librería [Sinon.JS](http://sinonjs.org/).
vplentinax marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The solution:
Solución:

```js run demo
function delay(f, ms) {
Expand All @@ -11,22 +11,22 @@ function delay(f, ms) {

let f1000 = delay(alert, 1000);

f1000("test"); // shows "test" after 1000ms
f1000("test"); // mostrar "test" después de 1000ms
```

Please note how an arrow function is used here. As we know, arrow functions do not have own `this` and `arguments`, so `f.apply(this, arguments)` takes `this` and `arguments` from the wrapper.
Tenga en cuenta cómo se utiliza una función de flecha aquí. Como sabemos, las funciones de flecha no tienen contextos propios `this` ni `argumentos`, por lo que `f.apply(this, arguments)` toma `this` y `arguments` del contenedor.

If we pass a regular function, `setTimeout` would call it without arguments and `this=window` (assuming we're in the browser).
Si pasamos una función regular, `setTimeout` lo llamaría sin argumentos y `this = window` (suponiendo que estemos en el navegador).

We still can pass the right `this` by using an intermediate variable, but that's a little bit more cumbersome:
Todavía podemos pasar el `this` correcto usando una variable intermedia, pero eso es un poco más engorroso:

```js
function delay(f, ms) {

return function(...args) {
let savedThis = this; // store this into an intermediate variable
let savedThis = this; // almacenar esto en una variable intermedia
setTimeout(function() {
f.apply(savedThis, args); // use it here
f.apply(savedThis, args); // úsalo aquí
}, ms);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ importance: 5

---

# Delaying decorator
# Decorador de retraso

Create a decorator `delay(f, ms)` that delays each call of `f` by `ms` milliseconds.
Cree un decorador `delay(f, ms)` que retrase cada llamada de `f` en `ms` milisegundos.

For instance:
Por ejemplo

```js
function f(x) {
alert(x);
}

// create wrappers
// crear contenedores
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);

f1000("test"); // shows "test" after 1000ms
f1500("test"); // shows "test" after 1500ms
f1000("test"); // mostrar "test" después de 1000ms
f1500("test"); // mostrar "test" después de 1500ms
```

In other words, `delay(f, ms)` returns a "delayed by `ms`" variant of `f`.
En otras palabras, `delay (f, ms)` devuelve una variante "Retrasada por `ms`" de`f`.

In the code above, `f` is a function of a single argument, but your solution should pass all arguments and the context `this`.
En el código anterior, `f` es una función de un solo argumento, pero en esta solución debe pasar todos los argumentos y el contexto `this`.
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ function debounce(func, ms) {

```

A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout.

Una llamada a `debounce` devuelve un contenedor wrapper. Cuando se le llama, planifica la llamada a la función original después de los `ms` dados y cancela el tiempo de espera anterior.
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@ importance: 5

---

# Debounce decorator
# Decorador debounce

The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments.
El resultado del decorador `debounce(f, ms)` es un contenedor que suspende las llamadas a `f` hasta que haya `ms` milisegundos de inactividad (sin llamadas, "período de enfriamiento"), luego invoca `f` una vez con los últimos argumentos.

For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`.
Por ejemplo, teníamos una función `f` y la reemplazamos con `f = debounce(f, 1000)`.

Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call.
Entonces, si la función contenedora se llama a 0ms, 200ms y 500ms, y luego no hay llamadas, entonces la 'f' real solo se llamará una vez, a 1500ms. Es decir: después del período de enfriamiento de 1000 ms desde la última llamada.

![](debounce.svg)

...And it will get the arguments of the very last call, other calls are ignored.
... Y obtendrá los argumentos de la última llamada, y se ignorarán otras llamadas.

Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce):
Aquí está el código para ello (usa el decorador debounce del [Lodash library](https://lodash.com/docs/4.17.15#debounce):

```js
let f = _.debounce(alert, 1000);

f("a");
setTimeout( () => f("b"), 200);
setTimeout( () => f("c"), 500);
// debounced function waits 1000ms after the last call and then runs: alert("c")
// la función debounce espera 1000 ms después de la última llamada y luego ejecuta: alert ("c")
```


Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished.
Ahora un ejemplo práctico. Digamos que el usuario escribe algo y nos gustaría enviar una solicitud al servidor cuando finalice la entrada.

There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result.
No tiene sentido enviar la solicitud para cada caracter escrito. En su lugar, nos gustaría esperar y luego procesar todo el resultado.

In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input.
En un navegador web, podemos configurar un controlador de eventos, una función que se llama en cada cambio de un campo de entrada. Normalmente, se llama a un controlador de eventos con mucha frecuencia, por cada tecla escrita. Pero si le pasamos `debounce` por 1000ms, entonces solo se llamará una vez, después de 1000ms después de la última entrada.

```online

In this live example, the handler puts the result into a box below, try it:
En este ejemplo en vivo, el controlador coloca el resultado en un cuadro a continuación, pruébelo:

[iframe border=1 src="debounce" height=200]

See? The second input calls the debounced function, so its content is processed after 1000ms from the last input.
¿Ve? La segunda entrada llama a la función debounce, por lo que su contenido se procesa después de 1000 ms desde la última entrada.
```

So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else.
Entonces, `debounce` es una excelente manera de procesar una secuencia de eventos: ya sea una secuencia de pulsaciones de teclas, movimientos del mouse u otra cosa.


It waits the given time after the last call, and then runs its function, that can process the result.
Espera el tiempo dado después de la última llamada y luego ejecuta su función, que puede procesar el resultado.

The task is to implement `debounce` decorator.
La tarea es implementar el decorador `debounce`.

Hint: that's just a few lines if you think about it :)
Sugerencia: son solo algunas líneas si lo piensas :)
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ function throttle(func, ms) {
}
```

A call to `throttle(func, ms)` returns `wrapper`.
Una llamada a `throttle(func, ms)` devuelve un `wrapper`.

1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`).
2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call.
3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context.
1. Durante la primera llamada, el `wrapper` solo ejecuta `func` y establece el estado de enfriamiento (`isThrottled = true`).
2. En este estado, todas las llamadas se memorizan en `savedArgs/savedThis`. Tenga en cuenta que tanto el contexto como los argumentos son igualmente importantes y deben memorizarse. Los necesitamos simultáneamente para reproducir la llamada.
3. Después de que pasan `ms` milisegundos, se activa `setTimeout`. El estado de enfriamiento se elimina (`isThrottled = false`) y, si ignoramos las llamadas,`wrapper` se ejecuta con los últimos argumentos y contexto memorizados.

The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it.
El tercer paso no ejecuta `func`, sino `wrapper`, porque no solo necesitamos ejecutar `func`, sino que una vez más ingresamos al estado de enfriamiento y configuramos el tiempo de espera para restablecerlo.
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@ importance: 5

---

# Throttle decorator
# Decorador throttle

Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
Crea un decorador "throttling" `throttle(f, ms)` -- que devuelve un contenedor.

When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
Cuando se llama varias veces, pasa la llamada a `f` como máximo una vez por `ms` milisegundos.

The difference with debounce is that it's completely different decorator:
- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
La diferencia con *debounce* es que es un decorador completamente diferente:
- `debounce` ejecuta la función una vez después del período de `enfriamiento`. Es bueno para procesar el resultado final.
- `throttle` lo ejecuta no más de lo que se le da en el tiempo `ms`. Es bueno para actualizaciones regulares que no deberían ser muy frecuentes.

Let's check the real-life application to better understand that requirement and to see where it comes from.
Revisemos una aplicación de la vida real para comprender mejor ese requisito y ver de dónde proviene.

**For instance, we want to track mouse movements.**
**Por ejemplo, queremos rastrear los movimientos del mouse.**

In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms).
**We'd like to update some information on the web-page when the pointer moves.**
En un navegador, podemos configurar una función para que se ejecute en cada movimiento del mouse y obtener la ubicación del puntero a medida que se mueve. Durante un uso activo del mouse, esta función generalmente se ejecuta con mucha frecuencia, puede ser algo así como 100 veces por segundo (cada 10 ms).
**Nos gustaría actualizar cierta información en la página web cuando se mueve el puntero.**

...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms.
...Pero la función de actualización `update()` es demasiado pesada para hacerlo en cada micro-movimiento. Tampoco tiene sentido actualizar más de una vez cada 100 ms.

So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms.
Entonces lo envolveremos en el decorador: use `throttle(update, 100)` como la función para ejecutar en cada movimiento del mouse en lugar del original `update()`. Se llamará al decorador con frecuencia, pero reenviará la llamada a `update()` como máximo una vez cada 100 ms.

Visually, it will look like this:
Visualmente, se verá así:

1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately.
2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls.
3. At the end of `100ms` -- one more `update` happens with the last coordinates.
4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed.
1. Para el primer movimiento del mouse, el variante decorador pasa inmediatamente la llamada a `update`. Eso es importante, el usuario ve nuestra reacción a su movimiento de inmediato
2. Luego, a medida que el mouse avanza, hasta `100ms` no sucede nada. La variante decorador ignora las llamadas.
3. Al final de`100ms` -- ocurre un `update` más con las últimas coordenadas.
4. Entonces, finalmente, el mouse se detiene en alguna parte. La variante decorador espera hasta que expire `100ms` y luego ejecuta `update` con las últimas coordenadas. Entonces, y esto es bastante importante, se procesan las coordenadas finales del mouse.

A code example:
Un código de ejemplo:

```js
function f(a) {
console.log(a);
}

// f1000 passes calls to f at maximum once per 1000 ms
// f1000 pasa llamadas a f como máximo una vez cada 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)
f1000(1); // muestra 1
f1000(2); // (throttling, 1000ms aún no)
f1000(3); // (throttling, 1000ms aún no)

// when 1000 ms time out...
// ...outputs 3, intermediate value 2 was ignored
// tiempo de espera de 1000 ms ...
// ...devuelve 3, el valor intermedio 2 fue ignorado
```

P.S. Arguments and the context `this` passed to `f1000` should be passed to the original `f`.
P.D Los argumentos y el contexto `this` pasado a `f1000` deben pasarse a la `f` original.
Loading