Угловой 2 с использованием RxJS - возьми (1) против первого ()
Я нашел несколько реализаций Auth Guards, которые используют take(1)
, На моем проекте я использовал first()
чтобы удовлетворить мои потребности. Это работает так же? Или один из них может иметь преимущества или около того.
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/first';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFire } from 'angularfire2';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private angularFire: AngularFire, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.angularFire.auth.map(
(auth) => {
if (auth) {
this.router.navigate(['/dashboard']);
return false;
} else {
return true;
}
}
).first(); // Just change this to .take(1)
}
}
3 ответа
операторы first()
а также take()
не то же самое.
first()
оператор принимает необязательный predicate
функция и испускает error
уведомление, когда значение не найдено, когда источник завершен.
Например, это выдаст ошибку:
Rx.Observable.empty()
.first()
.subscribe(console.log, err => console.log('Error', err));
... так же как и это:
Rx.Observable.range(1, 5)
.first(val => val > 6)
.subscribe(console.log, err => console.log('Error', err));
Пока это будет соответствовать первому излучаемому значению:
Rx.Observable.range(1, 5)
.first()
.subscribe(console.log, err => console.log('Error', err));
С другой стороны take(1)
просто принимает первое значение и завершает. Никакой дальнейшей логики не требуется.
Rx.Observable.range(1, 5)
.take(1)
.subscribe(console.log, err => console.log('Error', err));
Тогда с пустым исходным кодом Observable он не выдаст никакой ошибки:
Rx.Observable.empty()
.take(1)
.subscribe(console.log, err => console.log('Error', err));
Совет: используйте только first()
если:
- Вы считаете, что нулевые отправленные элементы являются ошибочным условием (например, завершение перед отправкой) И если вероятность ошибки превышает 0%, вы корректно обрабатываете их.
- ИЛИ Вы на 100% знаете, что наблюдаемый источник будет испускать 1+ предметов (поэтому никогда не может бросать).
Если выбросы равны нулю, и вы не обращаетесь с ними явно (с catchError
) тогда эта ошибка будет распространена, возможно, вызовет неожиданную проблему где-то еще и может быть довольно сложно отследить - особенно если она исходит от конечного пользователя.
Вы безопаснее от использования take(1)
по большей части при условии, что:
- Ты в порядке с
take(1)
ничего не излучать, если источник завершается без эмиссии. - Вам не нужно использовать встроенный предикат (например,
first(x => x > 10)
)
Примечание: вы можете использовать предикат с take(1)
как это: .pipe( filter(x => x > 10), take(1) )
, В этом нет ошибки, если нет ничего больше 10.
Как насчет single()
Если вы хотите быть еще строже и запретить два выброса, вы можете использовать single()
какие ошибки, если есть выбросы ноль или 2+. Опять же вам придется обрабатывать ошибки в этом случае.
Совет: Single
иногда может быть полезно, если вы хотите убедиться, что ваша наблюдаемая цепочка не выполняет дополнительную работу, например, дважды вызывает http-сервис и генерирует две наблюдаемые. Добавление single
до конца трубы сообщу, если вы допустили такую ошибку. Я использую его в "средстве выполнения задач", где вы передаете наблюдаемую задачу, которая должна выдавать только одно значение, поэтому я передаю ответ через single(), catchError()
чтобы гарантировать хорошее поведение.
Почему не всегда использовать first()
вместо take(1)
?
ака. Как может first
потенциально вызвать больше ошибок?
Если у вас есть наблюдаемое, которое берет что-то из сервиса, а затем передает его через first()
Вы должны быть в порядке большую часть времени. Но если кто-то приходит, чтобы отключить службу по какой-либо причине - и изменяет ее, чтобы испустить of(null)
или же NEVER
тогда любой вниз по течению first()
операторы начнут выбрасывать ошибки.
Теперь я понимаю, что это может быть именно то , что вы хотите - следовательно, это всего лишь совет. Оператор first
обратился ко мне, потому что это звучало немного менее "неуклюже", чем take(1)
но вы должны быть осторожны с обработкой ошибок, если есть вероятность, что источник не излучает. Хотя все будет зависеть от того, что вы делаете.
Если у вас есть значение по умолчанию (константа):
Рассмотрим также .pipe(defaultIfEmpty(42), first())
если у вас есть значение по умолчанию, которое следует использовать, если ничего не генерируется. Это, конечно, не вызывает ошибку, потому что first
всегда получит значение.
Обратите внимание, что defaultIfEmpty
срабатывает только в том случае, если поток пустой, а не в том случае, если значение null
,
Вот три наблюдаемых A
, B
, а также C
с мраморными диаграммами, чтобы изучить разницу между first
, take
, а также single
операторы:
* Легенда:
--o--
ценность
----!
ошибка
----|
завершение
Поиграйте с ним на https://thinkrx.io/rxjs/first-vs-take-vs-single/.
Уже имея все ответы, я хотел добавить более наглядное объяснение
Надеюсь, это кому-то поможет
Есть одно действительно важное отличие, которое нигде не упоминается.
take (1) испускает 1, завершает, отписывается
first() испускает 1, завершает, но не отписывается.
Это означает, что ваша наблюдаемая вверх по течению будет все еще горячей после first(), что, вероятно, не является ожидаемым поведением.
UPD: это относится к RxJS 5.2.0. Эта проблема может быть уже исправлена.
Похоже, что в RxJS 5.2.0 .first()
оператор имеет ошибку,
Из-за этой ошибки .take(1)
а также .first()
может вести себя совершенно иначе, если вы используете их с switchMap
:
С take(1)
вы получите поведение, как и ожидалось:
var x = Rx.Observable.interval(1000)
.do( x=> console.log("One"))
.take(1)
.switchMap(x => Rx.Observable.interval(1000))
.do( x=> console.log("Two"))
.subscribe((x) => {})
// In the console you will see:
// One
// Two
// Two
// Two
// Two
// etc...
Но с .first()
вы получите неправильное поведение:
var x = Rx.Observable.interval(1000)
.do( x=> console.log("One"))
.first()
.switchMap(x => Rx.Observable.interval(1000))
.do( x=> console.log("Two"))
.subscribe((x) => {})
// In console you will see:
// One
// One
// Two
// One
// Two
// One
// etc...
Вот ссылка на коде
Оказывается, есть очень важное различие между двумя методами: first() выдаст ошибку, если поток завершится до того, как будет выдано значение. Или, если вы указали предикат(i.e. first(value => value === 'foo'))
, он выдаст ошибку, если поток завершится до того, как будет выдано значение, прошедшее предикат.
take(1), с другой стороны, с радостью продолжит работу, если значение никогда не передается из потока. Вот простой пример:
const subject$ = new Subject();
// logs "no elements in sequence" when the subject completes
subject$.first().subscribe(null, (err) => console.log(err.message));
// never does anything
subject$.take(1).subscribe(console.log);
subject$.complete();
Другой пример с использованием предиката:
const observable$ = of(1, 2, 3);
// logs "no elements in sequence" when the observable completes
observable$
.first((value) => value > 5)
.subscribe(null, (err) => console.log(err.message));
// the above can also be written like this, and will never do
// anything because the filter predicate will never return true
observable$
.filter((value) => value > 5);
.take(1)
.subscribe(console.log);
Как новичка в RxJS, такое поведение меня очень сбивало с толку, хотя это была моя собственная ошибка, потому что я сделал несколько неверных предположений. Если бы я потрудился проверить документацию, я бы увидел, что поведение четко задокументировано:
Выдает ошибку, если
defaultValue
не был предоставлен, и соответствующий элемент не найден.
Причина, по которой я так часто сталкиваюсь с этим, - это довольно распространенный шаблон Angular 2, в котором наблюдаемые очищаются вручную во время OnDestroy
крючок жизненного цикла:
class MyComponent implements OnInit, OnDestroy {
private stream$: Subject = someDelayedStream();
private destroy$ = new Subject();
ngOnInit() {
this.stream$
.takeUntil(this.destroy$)
.first()
.subscribe(doSomething);
}
ngOnDestroy() {
this.destroy$.next(true);
}
}
Сначала код выглядит безобидным, но проблемы возникают, когда компонент уничтожается раньше. stream$
может испускать значение. Потому что я используюfirst()
, при уничтожении компонента выдается ошибка. Обычно я подписываюсь на поток только для того, чтобы получить значение, которое должно использоваться в компоненте, поэтому мне все равно, будет ли компонент уничтожен до того, как поток испускается. Из-за этого я начал использоватьtake(1)
почти во всех местах, где я раньше использовал first()
.
filter(fn).take(1)
немного более подробный, чем first(fn)
, но в большинстве случаев я предпочитаю немного больше подробностей обработке ошибок, которые в конечном итоге не влияют на приложение.
Также важно отметить: то же самое относится к last()
а также takeLast(1)
.