Я нашел несколько реализаций AuthGuard, которые используют 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 {
        return this.angularFire.auth.map(
            (auth) =>  {
                if (auth) {
                    this.router.navigate(['/dashboard']);
                    return false;
                } else {
                    return true;
                }
            }
        ).first(); // Just change this to .take(1)
    }
}

Ответы (6)

Операторы first () и take (1) не совпадают.

Оператор first () принимает необязательную функцию предикат и выдает уведомление об ошибке , если значение не найдено, когда источник завершен.

Например, это приведет к ошибке:

import { EMPTY, range } from 'rxjs';
import { first, take } from 'rxjs/operators';

EMPTY.pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));

... а так же:

range(1, 5).pipe(
  first(val => val > 6),
).subscribe(console.log, err => console.log('Error', err));

Хотя это будет соответствовать первому выданному значению:

range(1, 5).pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));

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

range(1, 5).pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));

Тогда с пустым источником Observable он не выдаст никакой ошибки:

EMPTY.pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));

Январь 2019: Обновлено для RxJS 6

Оказывается, существует очень важное различие между двумя методами: first () выдаст ошибку, если поток завершится до того, как будет выдано значение. Или, если вы предоставили предикат (т.е. 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();

Another example, using a predicate:

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);
  }
}

Код сначала выглядит безобидным, но проблемы возникают, когда компонент уничтожается до того, как поток $ может выдать значение. Поскольку я использую first (), при уничтожении компонента выдается ошибка. Обычно я подписываюсь на поток только для того, чтобы получить значение, которое должно использоваться в компоненте, поэтому меня не волнует, будет ли компонент уничтожен до того, как поток испускается. Из-за этого я начал использовать take (1) почти во всех местах, где раньше использовал first ().

filter (fn) .take (1) немного более подробный, чем first (fn), но в большинстве случаев я предпочитаю немного больше подробностей обработке ошибок, которые в конечном итоге не влияют на приложение.

Также важно отметить: то же самое относится к last () и takeLast (1).

ссылка

Есть одно действительно важное отличие, которое нигде не упоминается.

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... 

Вот ссылка на codepen

Вот три наблюдаемых A, Bи C с мраморными диаграммами, чтобы изучить разницу между first, операторы takeи single:

first vs take vs single operators comparison

* Легенда:
- o - значение
----! ошибка
---- | завершение

Play with it at https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Уже имея все ответы, я хотел добавить более наглядное объяснение

Надеюсь, это кому-то поможет

Совет: используйте только 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 (), большую часть времени все будет в порядке. Но если кто-то по какой-то причине придет и отключит службу и изменит ее на выдачу of (null) или НИКОГДА, тогда любые последующие операторы first () будут начать кидать ошибки.

Теперь я понимаю, что это может быть именно то, что вы хотите - поэтому это всего лишь подсказка. Оператор first понравился мне, потому что он звучал немного менее «неуклюже», чем take (1), но вам нужно быть осторожным при обработке ошибок, если когда-либо есть вероятность того, что источник не испускает . Однако все будет зависеть от того, что вы делаете.


Если у вас есть значение по умолчанию (константа):

Рассмотрите также .pipe (defaultIfEmpty (42), first ()), если у вас есть значение по умолчанию, которое следует использовать, если ничего не передается. Это, конечно, не вызовет ошибки, потому что first всегда будет получать значение.

Обратите внимание, что defaultIfEmpty запускается только в том случае, если поток пуст, а не если значение того, что передается, равно null.

2022 WebDevInsider