Создание своего собственного оператора в RxJS
Published: 2023-04-12 03:41:49

Это вольный перевод очень хорошей статьи
<https://netbasal.com/creating-custom-operators-in-rxjs-32f052d69457>

В этом материале вместо слова *наблюдаемый (Observable)* используется слово *издатель*, а вместо слова *наблюдатель (Observer)* - слово *подписчик.*

Что такое оператор? Это издатель, который принимает на вход другого
издателя. Простейший оператор будет выглядеть так:

```
function myOperator<T>(source:Observable<T>){
return source;
}
```

Этот оператор бесполезен, так как возвращает (или передаёт далее по
цепочке) того же издателя, что и получает, но тем не менее это
оператор!

Теперь давайте попробуем вернуть какого-нибудь другого издателя:

```
function myOperator<T>(source: Observable<T>){
return new Observable(subscriber=>{
subscriber.next(1);
subscriber.complete();
})
}
```
Этот оператор никак не использует входного издателя, а просто возвращает единицу.  
Теперь попробуем создать оператор, который будет отфильтровывать
значения `null` и `undefined` и не будет пускать их дальше по цепи.

```
function filterNil() {
    return function <T>(sourse: Observable<T>) {
        return new Observable(s => {
            sourse.subscribe(
                {
                    next: (v) => {
                        if (v!==undefined&&v!==null) {
                            s.next(v);
                        }
                    },
                    error: (error) => {
                        s.error();
                    },
                    complete: () => {
                        s.complete();
                    }
                }
            )
        })
    }
}
```

Почему мы обернули оператор в ещё одну функцию? Это для того, чтобы было
удобно передавать оператору какие-то аргументы. Теперь эта функция
возвращает оператор и вызов функции равносилен применению оператора.

Но что-то не так - мы только что создали утечку памяти. Каждый
издатель должен возвращать функцию `unsubscribe()`, которая выполняет все
необходимые по очистке памяти действия, а издатель в нашем операторе
такую функцию не возвращает. Для того, чтобы всё заработало как надо
добавим ещё одну строку

```
function filterNil() {
    return function <T>(sourse: Observable<T>) {
        return new Observable(s => {
            sourse.subscribe(
                {
                    next: (v) => {
                        if (v!==undefined&&v!==null) {
                            s.next(v);
                        }
                    },
                    error: (error) => {
                        s.error();
                    },
                    complete: () => {
                        s.complete();
                    }
                }
            )
            return () => s.unsubscribe(); <===
        })
    }
}
```

Конструкция `Observable.subscribe()` возвращает объект `Subscription`,
поэтому можно сократить код, показанный выше просто возвратив сам объект
подписки. Теперь при вызове `unsubscribe()` будет вызываться метод
`unsubscribe()` подписки, которую мы возвращаем.

```
function filterNil() {
    return function <T>(sourse: Observable<T>) {
        return new Observable(s => {
            return sourse.subscribe(
                {
                    next: (v) => {
                        if (v!==undefined&&v!==null) {
                            s.next(v);
                        }
                    },
                    error: (error) => {
                        s.error();
                    },
                    complete: () => {
                        s.complete();
                    }
                }
            )
        })
    }
}
```
Рекомендуется создавать свои операторы на основе существующих и вот как
будет выглядеть оператор `filterNil`, созданный при помощи оператора
`filter`
```
function filterNil() {
    return function <T>(source: Observable<T>) {
        return source.pipe(filter(v=>v!==undefined && v!=null));
    }
}
```
А так как операторы функции блока `pipe` сами по себе уже возвращают функции, то
есть оператор `filter` уже вернёт нужную нам функцию, принимающую и
возвращающую издателя, то можно сократить код до
```
function filterNil() {
return filter(v=>v!==undefined&&v!=null);
}
```