Цель
В этой статье я хочу рассказать тонкую разницу между операторами 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. Поизменяйте время в операторах, почувствуйте как это работает
Это всё. Надеюсь, статья была для вас полезной.
Спасибо :)
pikus_spb
Отлично написано, все стало ясно. Спасибо за пост.