ambil (1) vs pertama ()

137

Saya menemukan beberapa implementasi AuthGuards yang digunakan take(1). Dalam proyek saya, saya menggunakan first().

Apakah keduanya bekerja dengan cara yang sama?

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
sumber

Jawaban:

198

Operator first()dan take(1)tidak sama.

The first()Operator mengambil opsional predicatefungsi dan memancarkan errorpemberitahuan bila ada nilai cocok ketika sumber selesai.

Misalnya ini akan memunculkan kesalahan:

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

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

... dan juga ini:

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

Meskipun ini akan cocok dengan nilai pertama yang dipancarkan:

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

Di sisi lain take(1)hanya mengambil nilai pertama dan selesai. Tidak ada logika lebih lanjut yang terlibat.

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

Kemudian dengan sumber kosong yang bisa diamati itu tidak akan memunculkan kesalahan:

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

Jan 2019: Diperbarui untuk RxJS 6

martin
sumber
2
Sama seperti catatan, saya tidak mengatakan itu first()dan take()secara umum sama, yang saya pikir jelas, hanya itu first()dan take(1)itu sama. Saya tidak yakin dari jawaban Anda jika Anda pikir masih ada perbedaan?
Günter Zöchbauer
14
@ GünterZöchbauer Sebenarnya, perilaku mereka berbeda. Jika sumber tidak memancarkan apa pun dan menyelesaikan kemudian first()mengirim pemberitahuan kesalahan sementara take(1)tidak akan memancarkan apa pun.
martin
@martin, pada beberapa kasus ambil (1) tidak akan memancarkan sesuatu berarti mengatakan debugging kode akan lebih sulit?
Karuban
7
@ Karar Ini benar-benar tergantung pada usecase Anda. Jika tidak menerima nilai apa pun tidak terduga daripada yang saya sarankan untuk digunakan first(). Jika itu keadaan aplikasi yang valid saya akan pergi dengan take(1).
martin
2
Ini mirip dengan .NET's .First()vs.FirstOrDefault() (dan kalau dipikir-pikir juga .Take(1)dalam hal itu. Pertama membutuhkan sesuatu dalam koleksi dan memberikan kesalahan untuk koleksi kosong - dan keduanya FirstOrDefault()dan .Take(1)memungkinkan koleksi menjadi kosong dan kembali nulldan koleksi kosong masing-masing.
Simon_Weaver
45

Kiat: Hanya gunakan first()jika:

  • Anda menganggap nol item yang dipancarkan sebagai kondisi kesalahan (mis., Menyelesaikan sebelum memancarkan) DAN jika ada lebih dari 0% kemungkinan kesalahan Anda menanganinya dengan anggun
  • ATAU Anda tahu 100% bahwa sumber yang dapat diamati akan memancarkan 1+ item (jadi tidak pernah bisa melempar) .

Jika tidak ada emisi dan Anda tidak secara eksplisit menanganinya (dengan catchError) maka kesalahan itu akan diperbanyak, mungkin menyebabkan masalah yang tidak terduga di tempat lain dan mungkin cukup sulit untuk dilacak - terutama jika itu berasal dari pengguna akhir.

Anda lebih aman menggunakan take(1)sebagian besar asalkan:

  • Anda baik-baik saja dengan take(1)tidak memancarkan apa pun jika sumbernya selesai tanpa emisi.
  • Anda tidak perlu menggunakan predikat sebaris (mis. first(x => x > 10))

Catatan: Anda dapat menggunakan predikat dengan take(1)seperti ini: .pipe( filter(x => x > 10), take(1) ). Tidak ada kesalahan dengan ini jika tidak ada yang lebih besar dari 10.

Bagaimana dengan single()

Jika Anda ingin menjadi lebih ketat, dan melarang dua emisi Anda dapat menggunakan single()kesalahan mana jika ada nol atau 2+ emisi . Sekali lagi Anda harus menangani kesalahan dalam kasus itu.

Kiat: Singlesesekali dapat bermanfaat jika Anda ingin memastikan rantai yang dapat diamati tidak melakukan pekerjaan ekstra seperti memanggil layanan http dua kali dan memancarkan dua yang dapat diamati. Menambahkan singleke ujung pipa akan memberi tahu Anda jika Anda melakukan kesalahan seperti itu. Saya menggunakannya di 'pelari tugas' di mana Anda lulus dalam tugas yang bisa diamati yang seharusnya hanya memancarkan satu nilai, jadi saya meneruskan responsnya single(), catchError()untuk menjamin perilaku yang baik.


Kenapa tidak selalu menggunakan first()bukan take(1)?

alias. Bagaimana bisafirst berpotensi menyebabkan lebih banyak kesalahan?

Jika Anda memiliki sesuatu yang dapat diobservasi yang mengambil sesuatu dari sebuah layanan dan kemudian mengirimkannya melalui first()Anda, Anda akan baik-baik saja. Tetapi jika seseorang datang untuk menonaktifkan layanan untuk alasan apa pun - dan mengubahnya untuk dipancarkanof(null) atau NEVERkemudian first()operator hilir akan mulai melempar kesalahan.

Sekarang saya menyadari bahwa mungkin itulah yang Anda inginkan - karenanya mengapa ini hanyalah tip. Operator firstmenarik saya karena kedengarannya sedikit kurang 'canggung' daripadatake(1) tetapi Anda harus berhati-hati dalam menangani kesalahan jika ada kemungkinan sumber tidak memancarkan. Akan sepenuhnya tergantung pada apa yang Anda lakukan.


Jika Anda memiliki nilai default (konstan):

Pertimbangkan juga .pipe(defaultIfEmpty(42), first())jika Anda memiliki nilai default yang harus digunakan jika tidak ada yang dipancarkan. Ini tentu saja tidak akan menimbulkan kesalahan karenafirst akan selalu menerima nilai.

Catatan yang defaultIfEmptyhanya dipicu jika aliran kosong, bukan jika nilai dari apa yang dipancarkan null.

Simon_Weaver
sumber
Sadarilah bahwa singlememiliki lebih banyak perbedaan first. 1. Ini hanya akan memancarkan nilai complete. Ini berarti bahwa jika yang diobservasi memancarkan nilai tetapi tidak pernah selesai maka tunggal tidak akan pernah memancarkan nilai. 2. Untuk beberapa alasan jika Anda melewatkan fungsi filter singleyang tidak cocok dengan apa pun, itu akan mengeluarkan undefinednilai jika urutan asli tidak kosong, yang tidak demikian halnya first.
Marinos An
28

Berikut adalah tiga diamati A, Bdan Cdengan diagram marmer untuk mengeksplorasi perbedaan antara first, takedan singleoperator:

perbandingan pertama vs take vs operator tunggal

* Legenda : penyelesaian kesalahan
--o-- nilai
----!
----|

Mainkan dengan itu di https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Sudah memiliki semua jawaban, saya ingin menambahkan penjelasan yang lebih visual

Semoga ini bisa membantu seseorang

kos
sumber
12

Ada satu perbedaan yang sangat penting yang tidak disebutkan di mana pun.

ambil (1) emisi 1, selesai, berhenti berlangganan

first () memancarkan 1, selesai, tetapi tidak berhenti berlangganan.

Ini berarti bahwa upstream Anda yang diamati masih akan menjadi panas setelah pertama () yang mungkin bukan perilaku yang diharapkan.

UPD: Ini mengacu pada RxJS 5.2.0. Masalah ini mungkin sudah diperbaiki.

norekhov
sumber
Saya tidak berpikir salah satu berhenti berlangganan, lihat jsbin.com/nuzulorota/1/edit?js,console .
weltschmerz
10
Ya, kedua operator menyelesaikan langganan, perbedaannya terjadi pada penanganan kesalahan. Jika itu diamati tidak memancarkan nilai dan masih mencoba untuk mengambil nilai pertama menggunakan operator pertama itu akan menimbulkan kesalahan. Jika kita menggantinya dengan operator take (1) meskipun nilainya tidak ada di arus ketika berlangganan terjadi, itu tidak menimbulkan kesalahan.
noelyahan
7
Untuk memperjelas: keduanya berhenti berlangganan. Contoh dari @weltschmerz terlalu disederhanakan, tidak berjalan sampai bisa berhenti dengan sendirinya. Yang ini sedikit lebih diperluas: repl.it/repls/FrayedHugeAudacity
Stephan LV
10

Tampaknya dalam RxJS 5.2.0 .first()operator memiliki bug ,

Karena bug itu .take(1)dan .first()dapat berperilaku sangat berbeda jika Anda menggunakannyaswitchMap :

Dengan take(1)Anda akan mendapatkan perilaku seperti yang diharapkan:

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

Tetapi dengan .first()Anda akan mendapatkan perilaku yang salah:

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

Berikut ini tautan ke codepen

Artem
sumber