Подробное описание операторов RxJS — Часть 1 +3


Эта статья — перевод оригинальной статьи Ben Lesh "RxJS Operators In-Depth - Part 1". Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов ​

Что такое оператор? Почему они существуют? Observables - это «множества».

Первое, что нужно понять об операторах, - это почему они существуют. Они существуют, потому что observables как тип позволяют нам обрабатывать события (или значения во времени) как множества или набор вещей.

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

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

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

Операторы - это механизмы, которые могут выполнять операцию над observable, преобразовывая его в новый observable.

Реализация базового оператора

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

Базовый оператор "удвоения"

import { Observable, of } from 'rxjs';
 
const double = (source: Observable<number>) =>
  new Observable((subscriber) => {
    const subscription = source.subscribe({
      // Here we alter our value and "send it along" to our consumer.
      next: (value) => subscriber.next(2 * value),
      // We have to make sure errors and completions are also forwarded to the consumer.
      error: (err) => subscriber.error(err),
      complete: () => subscriber.complete(),
    });
    return () => {
      // We must make sure to tear down our subscription.
      // when the returned observable is finalized.
      subscription.unsubscribe();
    };
  });
 
// Usage like so:
 
of(1, 2, 3, 4).pipe(double).subscribe(console.log);
 
// Output:
// 2
// 4
// 6
// 8

Что такое Observable.prototype.pipe?

На этом этапе может возникнуть некоторая путаница в отношении того, что представляет собой этот метод pipe в нашем observable выше. Краткая версия? Pipe ничего не делает, кроме передачи экземпляра observable любым операторным функциям ((source: Observable ) => Observable )), которые вы передаете ему, по порядку, передавая возвращаемое значение каждого из них в следующая операторная функция в цепочке. Например, мы могли бы использовать наш оператор double выше более одного раза, просто дважды передав его методу pipe:

of(1, 2, 3, 4).pipe(double, double).subscribe(console.log);
 
// Output:
// 4
// 8
// 12
// 16
 

Это в точности эквивалентно этому, потому что каждый pipe оператор просто возвращает новый observable результат:

const doubled = of(1, 2, 3, 4).pipe(double);
 
const doubledAgain = doubled.pipe(double);
 
doubledAgain.subscribe(console.log);
 
// Output:
// 4
// 8
// 12
// 16

Создание переиспользуемого оператора с помощью функций высшего порядка

Что, если бы мы хотели создать оператор, который мог бы делать больше, чем просто «удвоить», что, если бы мы хотели иметь возможность «утроить» или умножить на произвольное число? Функциональное программирование и «функции высшего порядка» упрощают это. Проще говоря, «функция высшего порядка» - это функция, возвращающая функцию. Итак, в нашем случае мы могли бы создать оператор умножения, который позволил бы нам передать ему множитель, используя наш оператор double в качестве основы, например:

import { Observable, of } from 'rxjs';
 
const multiply = (multiplier: number) => (source: Observable<number>) =>
  new Observable((subscriber) => {
    const subscription = source.subscribe({
      next: (value) => subscriber.next(multiplier * value),
      error: (err) => subscriber.error(err),
      complete: () => subscriber.complete(),
    });
    return () => {
      subscription.unsubscribe();
    };
  });
 
// Usage like so:
 
of(1, 2, 3, 4).pipe(multiply(2)).subscribe(console.log);
 
// Output:
// 2
// 4
// 6
// 8

Более того, с помощью этой практики функционального программирования вы можете воссоздать double и повторно использовать его, как в нашем предыдущем примере, если вы того пожелаете; Это делается простым вызовом функции умножения и сохранением и повторным использованием возвращенной операторной функции:

const double = multiply(2);
 
const doubled = of(1, 2, 3, 4).pipe(double);
 
const doubledAgain = doubled.pipe(double);
 
doubledAgain.subscribe(console.log);
 
// Output:
// 4
// 8
// 12
// 16

Это подводит нас к немного сбивающей с толку терминологии, является ли умножение «оператором»? Или возвращает "оператор"? Это своего рода внимание к различиям которые настолько настолько малы, что они не важны, но в основной команде RxJS мы обычно называем функции высшего порядка операторами, а их возвращаемые значения - «операторными функциями». В конечном счете, то, как вы относитесь к этим вещам, не имеет значения, если люди вокруг вас понимают, о чем вы говорите. (Я также слышал «оператор» и «экземпляр оператора».)

Операторы "декларативны"

Да, да. Под капотом есть необходимые вещи. И/или вы в обязательном порядке добавляете операторские функции в pipe, или, как бы то ни было, вы обращаете на это внимание. Но операторы созданы таким образом, что их можно декларативно перемещать в наблюдаемом конвейере, и они могут иметь разные эффекты. Например, у вас есть оператор для фильтрации нечетных чисел. Результаты будут сильно отличаться в зависимости от того, куда вы положите это pipe'е. Но что интересно, вам действительно не нужно изменять окружающий код, операторы «оперируют» входящими значениями, не заботясь о том, что «вышестоящее» от них, кроме того, что входящий тип правильный (в данном случае число) :

import { of, OperatorFunction, map, filter } from 'rxjs';
 
const double = map((n: number) => 2 * n);
const onlyEvens = filter((n: number) => n % 2 === 0);
 
const source = of(1, 2, 3, 4, 5);
 
source.pipe(onlyEvens, double).subscribe(console.log);
// Logs: 4, 8
 
source.pipe(double, onlyEvens).subscribe(console.log);
// Logs: 2, 4, 6, 8, 10
 

Углубляемся: вы создаете функции декларативно.

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

Важные советы

При создании собственных операторов:

  1. Операторы должны быть тщательно проверены. (Об этом я расскажу в следующем посте).

  2. По возможности старайтесь повторно использовать операторы RxJS. Они очень хорошо протестированы и охватывают множество крайних случаев, о которых вы, возможно, даже не догадывались (см. Совет №1).

  3. Всегда проверяйте, правильно ли ваши операторы передают все 3 типа уведомлений: «next», «error» и «complete».

  4. Всегда убеждайтесь, что вы очищаете все подписки, созданные вашими операторами.

  5. Операторы обычно не должны (почти никогда) создавать подписки вне возвращаемого observable.




К сожалению, не доступен сервер mySQL