Skip to content

Обновление задач debounce, throttle в соответствии с en версией #1903

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

Merged
merged 19 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
18b85c6
Обновление задач debounce, throttle в соответствии с en версией
Oct 14, 2023
8e70df1
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
eee220d
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
1371f52
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
e6c9bff
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
8f5a13f
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
99d405d
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
b7db5ac
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
b758d8d
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
bd344f1
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
315e971
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
77502a1
Update 1-js/06-advanced-functions/09-call-apply-decorators/04-throttl…
Alexandre887 Oct 20, 2023
7a7ef75
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
3076e76
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
432646f
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
2403bac
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
4e49905
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
777819c
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
ccf8ef4
Update 1-js/06-advanced-functions/09-call-apply-decorators/03-debounc…
Alexandre887 Oct 20, 2023
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,15 +1,7 @@
function debounce(f, ms) {

let isCooldown = false;

function debounce(func, ms) {
let timeout;
return function() {
if (isCooldown) return;

f.apply(this, arguments);

isCooldown = true;

setTimeout(() => isCooldown = false, ms);
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), ms);
};

}
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
describe("debounce", function() {
before(function() {
describe('debounce', function () {
before(function () {
this.clock = sinon.useFakeTimers();
});

after(function() {
after(function () {
this.clock.restore();
});

it("вызывает функцию один раз в 'ms' мс", function() {
let log = '';
it('для одного вызова - запускается через "ms" миллисекунд', function () {
const f = sinon.spy();
const debounced = debounce(f, 1000);

function f(a) {
log += a;
}
debounced('test');
assert(f.notCalled, 'не вызывается сразу');
this.clock.tick(1000);
assert(f.calledOnceWith('test'), 'вызывается после 1000ms');
});

f = debounce(f, 1000);
it('для 3 вызовов - вызывает последний через "ms" миллисекунд', function () {
const f = sinon.spy();
const debounced = debounce(f, 1000);

f(1); // вызвана
f(2); // проигнорирована
debounced('a');
setTimeout(() => debounced('b'), 200); // проигнорирована
setTimeout(() => debounced('c'), 500); // вызвана
this.clock.tick(1000);

setTimeout(() => f(3), 100); // проигнорирована (слишком рано)
setTimeout(() => f(4), 1100); // вызвана (1000 мс истекли)
setTimeout(() => f(5), 1500); // проигнорирована (менее 1000 мс с последнего вызова)
assert(f.notCalled, 'не вызывается после 1000ms');

this.clock.tick(5000);
assert.equal(log, "14");
this.clock.tick(500);

assert(f.calledOnceWith('c'), 'вызывается после 1500ms');
});

it("сохраняет контекст вызова", function() {
it('сохраняет контекст вызова', function () {
let obj = {
f() {
assert.equal(this, obj);
}
},
};

obj.f = debounce(obj.f, 1000);
obj.f("test");
obj.f('test');
this.clock.tick(5000);
});

});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Функция <code>handler</code> вызывается на этом поле для ввода:
<br>
<input id="input1" placeholder="введите текст">

<p>

А на этом поле функция <code>handler</code> вызывается с применением debounce – <code>debounce(handler, 1000)</code>:
<br>
<input id="input2" placeholder="введите текст">

<p>
<button id="result">Функция <code>handler</code> помещает результат сюда</button>

<script>
function handler(event) {
result.innerHTML = event.target.value;
}

input1.oninput = handler;
input2.oninput = _.debounce(handler, 1000);
</script>
Original file line number Diff line number Diff line change
@@ -1,27 +1,12 @@
```js demo
function debounce(f, ms) {

let isCooldown = false;

function debounce(func, ms) {
let timeout;
return function() {
if (isCooldown) return;

f.apply(this, arguments);

isCooldown = true;

setTimeout(() => isCooldown = false, ms);
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), ms);
};

}
```

Вызов `debounce` возвращает обёртку. Возможны два состояния:
- `isCooldown = false` -- готова к выполнению.
- `isCooldown = true` -- ожидание окончания тайм-аута.

В первом вызове `isCoolDown = false`, поэтому вызов продолжается, и состояние изменяется на `true`.

Пока `isCoolDown` имеет значение `true`, все остальные вызовы игнорируются.
```

Затем `setTimeout` устанавливает его в `false` после заданной задержки.
Вызов `debounce` возвращает обёртку. При вызове он планирует вызов исходной функции через указанное количество `ms` и отменяет предыдущий такой тайм-аут.
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,49 @@ importance: 5

# Декоратор debounce

Результатом декоратора `debounce(f, ms)` должна быть обёртка, которая передаёт вызов `f` не более одного раза в `ms` миллисекунд.
Другими словами, когда мы вызываем `debounce`, это гарантирует, что все остальные вызовы будут игнорироваться в течение `ms`.
Результат декоратора `debounce(f, ms)` – это обёртка, которая откладывает вызовы `f`, пока не пройдёт `ms` миллисекунд бездействия (без вызовов, «cooldown period»), а затем вызывает `f` один раз с последними аргументами.

Например:
Другими словами, `debounce` – это так называемый секретарь, который принимает «телефонные звонки», и ждёт, пока не пройдет `ms` миллисекунд тишины. И только после этого передает «начальнику» информацию о последнем звонке (вызывает непосредственно `f`).

Например, у нас была функция `f` и мы заменили её на `f = debounce(f, 1000)`.

Затем, если обёрнутая функция вызывается в 0, 200 и 500 мс, а потом вызовов нет, то фактическая `f` будет вызвана только один раз, в 1500 мс. То есть: по истечению 1000 мс от последнего вызова.

![](debounce.svg)

...И она получит аргументы самого последнего вызова, остальные вызовы игнорируются.

Ниже код этого примера (используется декоратор debounce из библиотеки [Lodash](https://lodash.com/docs/4.17.15#debounce)):

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

f("a");
setTimeout( () => f("b"), 200);
setTimeout( () => f("c"), 500);

// Обёрнутая в debounce функция ждёт 1000 мс после последнего вызова, а затем запускает: alert("c")
```

f(1); // выполняется немедленно
f(2); // проигнорирован
Теперь практический пример. Предположим, пользователь набирает какой-то текст, и мы хотим отправить запрос на сервер, когда ввод этого текста будет завершён.

setTimeout( () => f(3), 100); // проигнорирован (прошло только 100 мс)
setTimeout( () => f(4), 1100); // выполняется
setTimeout( () => f(5), 1500); // проигнорирован (прошло только 400 мс от последнего вызова)
Нет смысла отправлять запрос для каждого набранного символа. Вместо этого мы хотели бы подождать, а затем обработать весь результат.

В браузере мы можем настроить обработчик событий – функцию, которая вызывается при каждом изменении поля для ввода. Обычно обработчик событий вызывается очень часто, для каждого набранного символа. Но если мы воспользуемся `debounce` на 1000мс, то он будет вызван только один раз, через 1000мс после последнего ввода символа.

```online

В этом живом примере обработчик помещает результат в поле ниже, попробуйте:

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

Видите? На втором поле вызывается функция, обёрнутая в `debounce`, поэтому его содержимое обрабатывается через 1000мс с момента последнего ввода.
```

На практике `debounce` полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового. Так что лучше не тратить на него ресурсы.
Таким образом, `debounce` – это отличный способ обработать последовательность событий: будь то последовательность нажатий клавиш, движений мыши или ещё что-либо.

Он ждёт заданное время после последнего вызова, а затем запускает свою функцию, которая может обработать результат.

Задача — реализовать декоратор `debounce`.

Подсказка: это всего лишь несколько строк, если вдуматься :)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ function throttle(func, ms) {

1. Во время первого вызова обёртка просто вызывает `func` и устанавливает состояние задержки (`isThrottled = true`).
2. В этом состоянии все вызовы запоминаются в `saveArgs / saveThis`. Обратите внимание, что контекст и аргументы одинаково важны и должны быть запомнены. Они нам нужны для того, чтобы воспроизвести вызов позднее.
3. ... Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.
3. Затем по прошествии `ms` миллисекунд срабатывает `setTimeout`. Состояние задержки сбрасывается (`isThrottled = false`). И если мы проигнорировали вызовы, то "обёртка" выполняется с последними запомненными аргументами и контекстом.

На третьем шаге выполняется не `func`, а `wrapper`, потому что нам нужно не только выполнить `func`, но и ещё раз установить состояние задержки и таймаут для его сброса.
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ importance: 5

# Тормозящий (throttling) декоратор

Создайте "тормозящий" декоратор `throttle(f, ms)`, который возвращает обёртку, передавая вызов в `f` не более одного раза в `ms` миллисекунд. Те вызовы, которые попадают в период "торможения", игнорируются.
Создайте «тормозящий» декоратор `throttle(f, ms)`, который возвращает обёртку.

**Отличие от `debounce` - если проигнорированный вызов является последним во время "задержки", то он выполняется в конце.**
При многократном вызове он передает вызов `f` не чаще одного раза в `ms` миллисекунд.

Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.
По сравнению с декоратором `debounce` поведение совершенно другое:
- `debounce` запускает функцию один раз после периода «бездействия». Подходит для обработки конечного результата.
- `throttle` запускает функцию не чаще, чем указанное время `ms`. Подходит для регулярных обновлений, которые не должны быть слишком частыми.

**Например, мы хотим отслеживать движения мыши.**
Другими словами, `throttle` похож на секретаря, который принимает телефонные звонки, но при этом беспокоит начальника (вызывает непосредственно `f`) не чаще, чем один раз в `ms` миллисекунд.

Давайте рассмотрим реальное применение, чтобы лучше понять это требование и выяснить, откуда оно взято.

В браузере мы можем объявить функцию, которая будет запускаться при каждом движении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, это может происходить около 100 раз в секунду (каждые 10 мс).
**Например, мы хотим отслеживать движения мыши.**

**Мы бы хотели обновлять информацию на странице при передвижениях.**
В браузере мы можем реализовать функцию, которая будет запускаться при каждом перемещении указателя и получать его местоположение. Во время активного использования мыши эта функция запускается очень часто, что-то около 100 раз в секунду (каждые 10 мс). **Мы бы хотели обновлять некоторую информацию на странице при передвижении указателя.**

...Но функция обновления `update()` слишком ресурсоёмкая, чтобы делать это при каждом микродвижении. Да и нет смысла делать обновление чаще, чем один раз в 1000 мс.

Expand Down