
Цель
В этой статье я хочу рассказать тонкую разницу между операторами debounceTime и throttleTime простыми словами
Общее
1) У нас есть метод в сервисе нашего Angular приложения
getData(id: number): Observable<RxjsResponse> { return this.httpClient.get<RxjsResponse>(`${this.basePath}/data/${id}`); }
Это простой метод, который обращается на backend и получает ответ в виде
{ id: number; name: string; }
2) Есть backend нашего приложения
justData.get('/data/:id', async (req: Request, res: Response): Promise<void> => { const id = req.params.id; if (id === '1') { res.status(200).send({ id: 1, name: 'Первый' }); } if (id === '2') { res.status(200).send({ id: 2, name: 'Второй' }); } if (id === '3') { res.status(200).send({ id: 3, name: 'Третий' }); } if (id === '4') { res.status(200).send({ id: 4, name: 'Четвёртый' }); } });
Это простой роут на бэке, который в зависимости от id возвращает определённый ответ
debounceTime
(Ну что, успокоился?)
Посмотрим как работает debounceTime, он проще для понимания
interval(1000) .pipe( take(5), filter((id) => id !== 0), debounceTime(1500), mergeMap((id) => this.rxjsService.getData(id)), tap((result) => console.log(result)) ) .subscribe();
Что тут происходит?
-
interval выкидывает целое число (0, 1, 2, 3……) каждую секунду
-
take(5) отпишется от interval после пяти эмитов (до следующего оператора filter дойдут только числа от 0 до 4)
-
filter((id) => id !== 0) не пропустит первый эмит (до следующего оператора debounceTime дойдут только idшки от 1 до 4)
А теперь рассмотрим что происходит после фильтра.
Оператор debounceTime не пропустит ни один эмит, к mergeMap, пока после предыдущего эмита не пройдёт то кол-во времени, которое указано в операторе. То есть в данном случае, когда единица дойдёт до debounceTime мы должны будем подождать 1.5 секунды, чтобы пройти с ней к mergeMap, но, так как у нас эмиты происходят каждую секунду, единица так и не пройдёт дальше, придёт двойка, поэтому про единицу можно забыть и так далее до конца. Догадываетесь какой будет результат?
Алгоритм:
-
Эмит 0 -> filter не пропустил
-
Эмит 1 -> id = 1 дошла до debounceTime, ждём полторы секунды
-
Эмит 2 -> id = 2 сэмитилась быстрее, чем время ожидания от debounceTime, забываем про id = 1, ждём 1.5 секунды с id = 2
-
Эмит 3 -> id = 3 сэмитилась быстрее, чем время ожидания от debounceTime, забываем про id = 2, ждём 1.5 секунды с id = 3
-
Эмит 4 -> id = 4 сэмитилась быстрее, чем время ожидания от debounceTime, забываем про id = 3, ждём 1.5 секунды
-
Больше эмитов нет, проходит 1.5 секунды, id = 4 двигается к mergeMap, делаем запрос на сервер
// Ответ { "id": 4, "name": "Четвёртый" }
Самый банальный пример использования: у вас есть input, который отвечает за поиск на сервере и отсылает запрос при изменении значения в input. Пользователь каждую секунду в этот поиск вносит новый символ. Зачем вам реагировать на каждое изменение и грузить сервер, когда можно отмести все лишние значения и уже когда пользователь перестанет вводить символы, пройдёт n секунд, уже пустить запрос на сервер?
throttleTime
(Открыть шлюз! Закрыть шлюз!)
interval(1000) .pipe( take(5), filter((id) => id !== 0), throttleTime(1900), mergeMap((id) => this.rxjsService.getData(id)), tap((result) => console.log(result)) ) .subscribe();
Код тот же самый, что и в предыдущем примере, только теперь у нас throttleTime(1900).
Оператор throttleTime, после первого пройденного через него эмита, не будет пропускать следующие эмиты в течении того количества времени, которое в нём указано. То есть в данном случае, когда единица пройдёт через throttleTime, клапан закроется на 1.9 секунд и, например, следующий эмит, который произойдёт через секунду к mergeMap не пройдёт, а третий уже пройдёт, потому что он будет сделан через 2 секунды, клапан к тому времени уже будет открыт, а потом снова закроется на 1.9 секунды
Алгоритм:
-
Эмит 0 -> filter не пропустил
-
Эмит 1 -> id = 1 прошёл через throttleTime, клапан закрылся, пошёл запрос на сервер
-
Эмит 2 -> id = 2 доходит до throttleTime, клапан закрыт, не проходит дальше
-
Эмит 3 -> id = 3 доходит до throttleTime, клапан открыт, проходит в mergeMap, клапан закрылся, пошёл запрос на сервер
-
Эмит 4 -> id = 4 доходит до throttleTime, клапан закрыт, не проходит дальше
//Ответ { "id": 1, "name": "Первый" } { "id": 3, "name": "Третий" }
Ещё один банальный абстрактный пример: вам нужно ограничить количество кликов по кнопке. Пользователь кликает по пять раз в секунду, а вам нужно пропускать только один клик в секунду, throttleTime(1000) вам в помощь
Заключение
Для тех, кто хочет сам потыкать на кнопки и посмотреть как работают операторы вот проекты на github. Поизменяйте время в операторах, почувствуйте как это работает
Это всё. Надеюсь, статья была для вас полезной.
Спасибо 🙂
ссылка на оригинал статьи https://habr.com/ru/post/715972/
Добавить комментарий