меню

Как front-end разработчик вы знаете, что управление асинхронными данными - это не легкая задача. Иногда кажется, что нужна целая команда клоунов, чтобы держать все эти мячи в воздухе! Но в этом и заключается сила библиотеки RxJS. Она помогает легко манипулировать потоками асинхронных данных.

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

Эти операторы - надежные помощники, на которых можно положиться, чтобы облегчить свою жизнь. Будь то фильтрация данных, выполнение побочных эффектов или обработка ошибок, эти операторы всегда рядом с вами. И мы покажем вам, как использовать их как профессионал.

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

Оператор map()

Давайте начнем с одного из самых базовых и универсальных операторов в арсенале RxJS: оператор map().

С помощью оператора map() вы можете преобразовывать данные, отправляемые observable, в что-то совершенно другое. Это похоже на фокусы мага, только вместо того, чтобы вытаскивать кролика из шляпы, вы преобразуете данные, как настоящий профессионал. Например, предположим, что у вас есть observable, который передает массив цен. С помощью оператора map() вы можете преобразовать поток цен в новый поток объектов, содержащих название каждого товара и его цену в более читаемом формате.

 
import { from } from 'rxjs';
import { map } from 'rxjs/operators';

const prices = from([10, 20, 30]);
const productsWithPrice = numbers.pipe(map((price, index) => ({
 name: `product #${index+1}`,
 price: price
})));
productsWithPrice.subscribe(result => console.log(result));

/* Вывод:
 { name: `product #1`, price: 10 },
 { name: `product #2`, price: 20 },
 { name: `product #3`, price: 30 }
*/
 

Вы только, что преобразовали скучный поток цен в элегантный поток объектов продуктов.

Оператор filter()

А что, если вы хотите работать только с частью данных, которые выдает observable? В этом случае используется filter(). Фильтр позволяет выбирать значения, которые нужно сохранить, и те, которые нужно отбросить. Например, если у вас есть observable, который выдает поток имен, вы можете использовать filter(), чтобы оставить только имена, которые начинаются с буквы "J". Просто и легко!

 
import { from } from 'rxjs';
import { filter } from 'rxjs/operators';

const names = from(['Joseph', 'Peter', 'Jamal', 'Ludmila', 'Kelly']);
const filtered = names.pipe(filter(name => name.at(0) === 'J'));
filtered.subscribe(result => console.log(result));

// Вывод:
// "Joseph"
// "Jamal"
 

Вместе оператор map() и оператор filter(), как динамический дуэт операторов RxJS, идеально работают вместе для преобразования и фильтрации потоков данных, делая их неотъемлемым инструментом любого Angular разработчика.

Оператор tap()

Я использую оператор tap() для выполнения побочных эффектов в моих потоках данных. Это может быть полезно для логирования, отладки или запуска других операций, которые не изменяют сам поток данных.

Оператор tap() принимает функцию, которая вызывается для каждого значения, выделяемого исходным observable, но не изменяет значение или поток. Вместо этого он позволяет вам сделать что-то со значением, например, залогировать его в консоль, прежде чем оно перейдет к следующему оператору в потоке данных.

Вот пример использования оператора tap() для логирования каждого значения, выделяемого наблюдаемым объектом:

  
mport { from } from 'rxjs';
import { tap } from 'rxjs/operators';

const numbers = from([1, 2, 3]);
const logged = numbers.pipe(
  tap(value => console.log(`Value emitted: ${value}`))
);
logged.subscribe();

// Вывод: 
// Value emitted: 1 
// Value emitted: 2
// Value emitted: 3
 

Один из моих любимых способов использования оператора tap() - это отображение уведомления на основе результата Observable. Просто добавив функцию tap() в Observable, я могу легко вычислить или подтвердить переданные значения и показать уведомление пользователю.

Оператор switchMap()

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

В своей основе оператор switchMap() позволяет преобразовывать элементы, излучаемые потоком Observable, в новые потоки Observable. Каждый раз при появлении нового элемента switchMap() отписывается от предыдущего потока Observable (если такой был) и подписывается на новый поток.

Оператор switchMap()

Вот так работает switchMap():

 
import { fromEvent } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const userStreams = clicks.pipe(
  switchMap(() => {
    const userIds = [1, 2, 3];
    return from(userIds).pipe(
      switchMap(id => {
        const userData = { id, name: `User ${id}` };
        return of(userData);
      })
    );

  })
);

userStreams.subscribe(user => console.log(user));
 

Если у вас есть поток событий кликов и вы хотите получить данные пользователя для каждого клика, вы можете использовать switchMap() для преобразования каждого события клика в новый поток Observable данных пользователей. Когда новое событие клика поступает, switchMap отменяет все текущие потоки пользователей, созданные из предыдущих событий кликов, и создает новые потоки пользователей на основе нового события клика. Это позволяет избежать возможных состязаний или конфликтов, которые могут возникнуть при одновременной обработке нескольких активных запросов. В контексте HTTP-запросов оператор switchMap() невероятно полезен, поскольку он позволяет отменять предыдущие запросы и переключаться на новый, гарантируя, что будет возвращен только самый последний запрос и избегая несоответствий данных.

Оператор combineLatest()

Оператор combineLatest() - это еще один полезный оператор RxJS, который позволяет объединить последние значения, отправленные несколькими потоками observable, в один поток. Каждый раз, когда любой из исходных потоков observable отправляет новое значение, оператор combineLatest() немедленно отправляет массив, содержащий последние значения из всех исходных потоков.

Например, если у вас есть два потока данных, поток-1 и поток-2, вы можете использовать combineLatest() для отправки массива последних значений из обоих потоков:

  
combineLatest([stream1, stream2])
 .subscribe(([latestValueFromStream1, latestValueFromStream2]) => {
    console.log(`Latest values: ${latestValueFromStream1} and ${latestValueFromStream2}`);
  }
);
 

Оператор debounceTime()

Если вы устали от того, что предложения поиска или функционал автозаполнения затрудняют ввод значений путем огромного количества значений, пока вы еще набираете текст, попробуйте использовать оператор debounceTime()! Этот удобный оператор задерживает отправку значений из потока observable до тех пор, пока не пройдет определенное количество времени без отправки новых значений, что позволяет вам немного отдохнуть перед просмотром окончательных результатов. Для этого оператора диаграмма выглядит следующим образом:

Оператор debounceTime()

Например, если у вас есть поле ввода, которое отправляет значения по мере набора пользователем текста, вы можете использовать debounceTime(), чтобы отправить значение из потока observable только после того, как пользователь перестал набирать текст на определенное количество времени:

  
inputField.valueChanges.pipe(
  debounceTime(500) // wait for 500ms of inactivity before emitting the latest value
).subscribe(latestValue => {
  console.log(`Latest value: ${latestValue}`);
});
  

С помощью оператора debounceTime() я могу задержать отправку значений из поля ввода, гарантируя, что будет отправлено только последнее значение после задержки в 500 мс. Это может помочь сократить ненужные запросы к API и улучшить общую производительность вашего приложения.

Оператор catchError()

Допустим, вы создаете приложение, которое получает данные из API, и не всегда все идет по плану: ваш сервер может быть недоступен или медленным, а также могут возникать ошибки с кодами ответа 404, 403 и другие. В этом случае оператор catchError() будет весьма полезен! Он позволяет "красиво" обрабатывать ошибки, возникающие в потоке Observable, таким образом, чтобы они не портили работу остального кода.

  
getDataFromApi().pipe(
  map(response => response.data),
  catchError(error => {
    console.log(`Error fetching data: ${error}`);
    return of([]); // return an empty array to continue the observable chain
  })
).subscribe(data => {
  console.log(`Received data: ${data}`);
});
 

Здесь мы имеем цепочку Observable, которая получает данные из API с использованием функции getDataFromApi(), затем используется оператор map() для извлечения свойства данных из объекта ответа. Однако, поскольку мы работаем с API, могут возникать ошибки, и в этом случае оператор catchError() обрабатывает ошибки "элегантно", регистрируя их и возвращая пустой массив, что позволяет цепочке Observable продолжить работу.

Заключение.

В данной статье мы рассмотрели лишь некоторые из семи операторов RxJS, доступных фронтенд-разработчикам для создания реактивных приложений. Корректное использование этих операторов позволит обеспечить быструю реакцию вашего приложения на действия пользователя и улучшить пользовательский опыт. Однако следует помнить, что все это лишь малая часть возможностей, которые предоставляет RxJS. Если вы хотите улучшить свои навыки реактивного программирования, не ограничивайтесь только изучением этих операторов - есть еще множество других полезных функций. Обязательно ознакомьтесь с другими статьями на эту тему, чтобы расширить свой кругозор. А какой оператор RxJS вам больше всего нравится?

Присоединяйтесь к нашему Tелелеграм каналу и ВК группе!


Возможно, вам будет интересно

Объекты в JavaScript

В статье рассмотрены базовые понятия, связанные с объектами в JavaScript: определение объектов, создание объектов, базовые операции с объектами

Восемь типов данных и typeof

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

Велосипеды на Javascript и jQuery

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

Собственные социальные share-кнопки

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

Оформление заявки

Документы на создание сайта

Изучите наше коммерческое предложение, заполните БРИФ и отправьте его на почту maxidebox@list.ru. Изучив все пожелания из БРИФ-а, обратным ответом оповестим Вас по стоимости разработке, ответим на вопросы.

КП на создание сайта Коммерческое предложение на созданеи сайта

Мы берем на себя ответственность за все стадии работы и полностью избавляем клиентов от забот и необходимости вникать в тонкости.

Скачать БРИФ (акета) на создание сайта Скачать БРИФ (акета) на создание сайта

Зополните у БРИФ-а все необходимые поля. Сделайте краткое описание к каждому из пунктов анкеты, привидите примеры в соответсвующий пунктах - это позволит лучше понять Ваши ожидания и требования к сайту