Не могли бы вы потерпеть меня на мгновение? Я знаю, что обещал что-то в названии, но я хочу сделать небольшой экскурс в деловой мир, прежде чем перейти к нему.

Что касается фронтенд-разработчиков, нам нужно быть ближе к нашим пользователям. В разных мирах их называют клиентами. Мир обслуживания клиентов постоянно меняется; Возьмем для примера вариант использования клиента, который обращается за поддержкой.

Если вы обращаетесь в большую компанию с просьбой о поддержке или с жалобой по старому методу, вы сталкиваетесь с ребятами из службы поддержки. Они открывают тикет на ваш запрос, и тикет запускает поток компании, пока он не будет разрешен. Если вы снова позвоните в службу поддержки, вы ударите другого человека, и он откроет новый билет, если нет необходимости закрывать старый.

Теперь компании понимают, что такой подход выглядит немного холодным и далеким, особенно в мире, где все мы связаны и все происходит в Интернете. Итак, с помощью социальных сетей они представили новый способ подключения с помощью окна чата. Таким образом, клиент заполняет открытую строку в компании, которая более близка к другим связям, которые у него есть со своими друзьями и семьей.

Мы можем использовать упомянутые выше подходы, чтобы понять, как справляться с некоторыми проблемами, с которыми мы сталкивались при работе с пользовательским вводом. Теперь я слышу, как вы, наркоманы кода, кричите: «Прекратите метафоры! просто покажи мне код! » так что давайте рассмотрим это.

Приближение событий / открытие билета

Для этого примера возьмем простой поисковый ввод. Ставим поисковую строку в наше приложение; Пользователь вводит запрос, и нам нужно найти его на сервере и представить результаты пользователю.

Наш код выглядит так:

В этом методе мы обычно слушаем событие ввода и отправляем запрос на сервер. На данный момент наша вкладка сети в инструментах разработчика выглядит так:

Мы видим, что мы «открываем тикет» при каждом вводе пользователя, отправляем каждый запрос на сервер и слушаем каждый ответ.

Если мы хотим придерживаться подхода событий, нам нужно решить несколько вещей самостоятельно:

  1. Debounce - мы не хотим отправлять на сервер каждый клик, сделанный пользователем; мы хотим немного подождать и отправить слово пользователя, а не каждую вводимую им букву. Найти подходящее время для дребезга - это своего рода искусство. Мы не хотим раздражать пользователя, и в то же время мы хотим, чтобы между звонками было достаточно времени.
  2. Состояние гонки - нам нужен только ответ последнего вызова, но мы получим их все, и мы не можем контролировать порядок. Во время разработки это может выглядеть нормально, но нам всегда нужно помнить о наших пользователях. У них может быть не очень быстрый компьютер для разработки, а иногда и хорошее сетевое соединение, и все может запутаться.

Это несложные проблемы. Мы можем разработать собственные механизмы устранения неполадок, таймер и некоторые дополнительные переменные для его отслеживания, и это не так уж сложно. Мы также можем обработать состояние гонки, используя метод отмены предыдущего HTTP-вызова, опять же не слишком сложный, но требующий других переменных или наблюдаемых в нашем коде.

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

Подход открытого потока / открытый чат

Итак, давайте возьмем вводимые пользователем данные и преобразуем их в поток. Если мы используем простой HTML-элемент ввода, мы можем сделать это с помощью rxjs fromEvent.

Наш код будет выглядеть так:

Теперь, когда у нас есть поток, мы можем использовать все преимущества rxjs. В нашем случае, поскольку нам нужен только ответ последнего вызова API, мы можем использовать switchMap. Этот оператор удобен в ситуации состояния гонки - всякий раз, когда в поток поступает новое значение, а старое значение еще не было разрешено, он отменяет старое и переключается вместе с ним. Если мы не получили ответа от предыдущего вызова, он будет отменен, и начнется новый вызов с новым значением.

Теперь наша вкладка сети выглядит так:

Мы получаем ответ только от последнего вызова API. Мы воспринимаем вводимые пользователем данные как поток, как будто ведем открытый разговор. Мы отвечаем пользователю на то, что он спросил сейчас, а не на то, что он просил ранее.

Итак, мы справились с состоянием гонки, и теперь мы можем легко справиться с отказом, используя оператор debounceTime. Этот оператор будет ждать, пока не произойдет одно из двух: либо завершится debounceTime, либо в поток поступит новое значение. Если время закончилось - переходите к следующему шагу; если введено новое значение - перезапустите таймер.

Поскольку нам не нужен отладчик для обработки состояния гонки, мы можем выбрать, хотим ли мы отладить (вот почему он прокомментировал код выше), и установить минимальное значение для лучшего взаимодействия с пользователем.

Вы заметили, что здесь произошло? Мы справились с двумя проблемами и добавили только одну строку кода для каждой. Нет необходимости добавлять ненужные члены класса, таймеры и логику; у нас все это есть в rxjs; нам просто нужно стримить.

Преобразование кнопки в поток

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

Нам не нужен дебонс, поскольку мы не запускаем поиск, пока пользователь печатает. Но нам нужен другой сценарий. Вы, наверное, знаете этих надоедливых пользователей; если они не получат ответ немедленно, они будут нажимать кнопку снова и снова, пока что-то не произойдет на экране или пока компьютер не начнет курить 🔥. Если мы не будем иметь дело с этими пользователями, то каждый клик будет вызывать новый вызов сервера, а мы этого не хотим.

Итак, давайте преобразуем событие нажатия кнопки в поток:

Что же мы имеем здесь?

  • В первом примере мы преобразовали событие элемента HTML в поток с помощью fromEvent. Но мы не всегда используем простые элементы HTML. Иногда мы используем какой-то компонент-оболочку, который генерирует событие. Возможно, вы используете один из них, если в вашем приложении есть библиотека компонентов пользовательского интерфейса. В этом примере предположим, что у нас есть компонент, который обертывает кнопку и генерирует событие (buttonClick) при каждом нажатии. Обычно мы связываемся с output в HTML так же, как и с событиями. Но если мы получаем ссылку на компонент напрямую (с @viewChild), мы можем подписаться на вывод, чтобы получить поток.
  • Затем мы сопоставляем событие щелчка, чтобы вернуть введенный пользователем запрос.
  • А вот и волшебство - мы используем distinctUntilChanged, еще один классный оператор rxjs, чтобы сказать потоку прекратить переход к следующему шагу, если значение не изменилось.
  • Если значение изменилось, мы переходим к switchMap и отправляем вызов на сервер. Шансы на условия гонки уменьшаются, но они все еще существуют для пользователей с быстрыми пальцами.

Теперь мы все покрыли; мы можем позволить пользователю отвлечься. Если значение не изменилось, мы игнорируем несколько кликов. И если он изменился, а сервер все еще не ответил на предыдущий вызов, мы отменяем его и отправляем новый.

Заключение

Преобразование пользовательского ввода в поток довольно просто и очень удобно, когда нам нужно обрабатывать запрос пользователя в контексте его предыдущих запросов. Я думаю, что поток представляет эти взаимодействия лучше, чем события. Мы ведем беседу с нашими пользователями, а не с механизмом действия и противодействия Павлова. Но, прежде всего, это делает наш код чище и меньше.

Вы можете использовать этот подход с другими взаимодействиями с пользователем и найти операторы rxjs, которые соответствуют вашим потребностям.

Если вы дожили до этого места и думаете о других реализациях, вы можете поделиться ими с нами в комментарии 💬.