Подтвердить что ты не робот

Angular 2 с использованием RxJS - take (1) vs first()

Я нашел несколько реализаций Auth Guard, которые используют 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)
    }
}
4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Совет: используйте 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() только одно значение, поэтому я single(), catchError() ответ через single(), catchError() чтобы гарантировать хорошее поведение.


Почему бы не всегда использовать first() вместо take(1)?

ака. Как first может вызвать больше ошибок?

Если у вас есть наблюдаемое, которое берет что-то из службы, а затем направляет это через first() вас все будет хорошо в большинстве случаев. Но если кто-то придет отключить службу по какой-либо причине - и изменит ее на emit of(null) или NEVER то любые последующие операторы first() начнут выдавать ошибки.

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


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

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

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

Ответ 3

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

take (1) испускает 1, завершает, отписывается

first() испускает 1, завершает, но не отписывается.

Это означает, что ваша наблюдаемая вверх по течению будет все еще горячей после first(), что, вероятно, не является ожидаемым поведением.

UPD: это относится к RxJS 5.2.0. Эта проблема может быть уже исправлена.

Ответ 4

Похоже, что в 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

Ответ 5

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

first vs take vs single operators comparison

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

Поиграйте с ним по адресу https://thinkrx.io/rxjs/first-vs-take-vs-single/.

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

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