взяти (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)
    }
}

Відповіді:


197

Оператори 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)те саме. Я не впевнений у вашій відповіді, якщо ви вважаєте, що все-таки є різниця?
Гюнтер Зехбауер

14
@ GünterZöchbauer Власне, їх поведінка відрізняється. Якщо джерело нічого не випромінює і завершує, то first()надсилайте сповіщення про помилки, тоді як take(1)просто нічого не випромінюєте.
март

@martin, у деяких випадках take (1) нічого не випромінює, сказати, що налагодження коду буде складніше?
Карубан

7
@Karuban Це дійсно залежить від вашої справи. Якщо отримання будь-якого значення є несподіваним, ніж я б запропонував використовувати first(). Якщо це дійсний стан заявки, я б пішов take(1).
март

2
Це схоже на .NET .First()vs .FirstOrDefault()(і подумайте про це також .Take(1)у тому, що Перший вимагає чогось у колекції і видає помилку для порожньої колекції - і те і інше, 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()вас, ви повинні будете чудово більшу частину часу. Але якщо хтось придумає відключити послугу з будь-якої причини - і змінить її на випромінювання, of(null)або NEVERтоді будь-який first()оператор нижче починає видавати помилки.

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


Якщо у вас є значення за замовчуванням (постійне):

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

Зауважте, що defaultIfEmptyспрацьовує лише у тому випадку, якщо потік порожній, а не якщо значення випромінюється null.


Майте на увазі, що singleмає більше відмінностей first. 1. Він буде випромінювати лише значення на complete. Це означає, що якщо спостережуване видає значення, але ніколи не завершує, одиничне ніколи не випромінює значення. 2. З якоїсь причини, якщо ви передасте функцію фільтра, singleякій нічого не відповідає, вона видаватиме undefinedзначення, якщо початкова послідовність не порожня, що не стосується first.
Марінос

28

Ось три Спостережувані A, Bі Cз мармуровими діаграмами , щоб дослідити різницю між first, takeі singleоператори:

перше порівняння порівняно з одним оператором

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

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

Маючи всі відповіді, я хотів додати більш наочне пояснення

Сподіваюся, це комусь допоможе


12

Є одна дійсно важлива різниця, яка ніде не згадується.

take (1) випускає 1, завершує, скасовує підписки

first () випускає 1, завершує, але не скасовує підписку.

Це означає, що ваше спостереження за течією все ще буде гарячим після першого (), що, мабуть, не очікується.

UPD: Це стосується RxJS 5.2.0. Ця проблема може бути вже виправлена.


Я не думаю, що жодна скасована підписка , див. 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... 

Ось посилання на codepen

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.