Разница между debounceTime и throttleTime

от автора

Цель

В этой статье я хочу рассказать тонкую разницу между операторами 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *