взять (1) против первого ()

137

Я нашел несколько реализаций, 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> | 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)
    }
}
Karuban
источник

Ответы:

198

Операторы 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()и take()в целом они такие же, что я думаю, очевидно, только то, что first()и take(1)такие же. Я не уверен в вашем ответе, считаете ли вы, что разница все еще есть?
Günter Zöchbauer
14
@ GünterZöchbauer На самом деле, их поведение другое. Если источник ничего не испускает и завершает работу, first()отправьте уведомление об ошибке, пока take(1)просто ничего не испускает.
Мартин
@martin, в некоторых случаях take (1) ничего не выдаст означает, что отладка кода будет сложнее?
Karuban
7
@Karuban Это действительно зависит от вашего варианта использования. Если не получить никакого значения неожиданно, я бы предложил использовать first(). Если это допустимое состояние приложения, я бы выбрал take(1).
Мартин
2
Это похоже на .NET .First()vs .FirstOrDefault()(и, если подумать, это еще и .Take(1)то, что First требует что-то в коллекции и выдает ошибку для пустой коллекции - и то и другое, FirstOrDefault()и .Take(1)позволяет коллекции быть пустой и возвращать nullи пустую коллекцию соответственно.
Simon_Weaver
45

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

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


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

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

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

Simon_Weaver
источник
Имейте в виду, что singleздесь больше различий first. 1. Он будет выдавать только значение complete. Это означает, что если наблюдаемый излучает значение, но никогда не завершается, то single никогда не будет генерировать значение. 2. По какой-то причине, если вы передадите функцию фильтра, singleкоторой ничего не соответствует, она выдаст undefinedзначение, если исходная последовательность не пуста, что не относится к случаю first.
Маринос Ан,
29

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

сравнение first vs take vs single

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

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

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

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

кос
источник
12

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

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

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

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

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

norekhov
источник
Не думаю, что кто-то откажется от подписки, см. Jsbin.com/nuzulorota/1/edit?js,console .
weltschmerz
10
Да, оба оператора оформляют подписку, разница в обработке ошибок. Если этот наблюдаемый объект не испускает значений и все же пытается принять первое значение с помощью первого оператора, он выдаст ошибку. Если мы заменим его оператором take (1), даже если значение отсутствует в потоке, когда происходит подписка, это не вызывает ошибки.
noelyahan
7
Для уточнения: оба вообще отписываются. Пример из @weltschmerz был слишком упрощен, он не запускается, пока не сможет самостоятельно отписаться. Этот немного более расширен: repl.it/repls/FrayedHugeAudacity
Stephan LV
10

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

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

Артем
источник