lấy (1) so với đầu tiên ()


137

Tôi tìm thấy một vài thực hiện AuthGuardsử dụng take(1). Trong dự án của tôi, tôi đã sử dụngfirst() .

Cả hai làm việc theo cùng một cách?

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

Câu trả lời:


197

Toán tử first()take(1)không giống nhau.

Các first()nhà điều hành có một tùy chọn predicatechức năng và phát ra một errorthông báo khi không có giá trị khớp lệnh khi nguồn hoàn thành.

Ví dụ, điều này sẽ phát ra một lỗi:

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

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

... cũng như thế này:

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

Trong khi điều này sẽ khớp với giá trị đầu tiên được phát ra:

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

Mặt khác take(1)chỉ cần lấy giá trị đầu tiên và hoàn thành. Không có logic hơn có liên quan.

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

Sau đó, với nguồn trống Có thể quan sát được, nó sẽ không phát ra bất kỳ lỗi nào:

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

Tháng 1 năm 2019: Cập nhật cho RxJS 6


2
Cũng như lưu ý, tôi đã không nói điều đó first()take()nói chung là giống nhau, điều mà tôi nghĩ là hiển nhiên, chỉ có điều đó first()take(1)giống nhau. Tôi không chắc chắn từ câu trả lời của bạn nếu bạn nghĩ vẫn còn một sự khác biệt?
Günter Zöchbauer

14
@ GünterZöchbauer Thật ra, hành vi của họ là khác nhau. Nếu nguồn không phát ra bất cứ thứ gì và hoàn thành thì hãy first()gửi thông báo lỗi trong khi take(1)đơn giản là sẽ không phát ra bất cứ thứ gì.
martin

@martin, trong một số trường hợp, (1) sẽ không phát ra bất cứ điều gì có nghĩa là để gỡ lỗi mã sẽ khó hơn?
Karuban

7
@Karuban Điều này thực sự phụ thuộc vào usecase của bạn. Nếu không nhận được bất kỳ giá trị nào là bất ngờ hơn tôi đề nghị sử dụng first(). Nếu đó là một trạng thái ứng dụng hợp lệ, tôi sẽ sử dụng take(1).
martin

2
Điều này tương tự với .NET .First()vs .FirstOrDefault()(và cũng nghĩ về điều đó .Take(1)trong đó Đầu tiên yêu cầu một cái gì đó trong bộ sưu tập và đưa ra lỗi cho một bộ sưu tập trống - và cả hai FirstOrDefault().Take(1)cho phép bộ sưu tập trống và trả lại nullvà bộ sưu tập trống tương ứng.
Simon_Weaver

44

Mẹo: Chỉ sử dụng first()nếu:

  • Bạn coi các mục không phát ra là một điều kiện lỗi (ví dụ: hoàn thành trước khi phát) nếu có khả năng xảy ra lỗi lớn hơn 0%, bạn sẽ xử lý nó một cách duyên dáng
  • HOẶC Bạn biết 100% rằng nguồn có thể quan sát được sẽ phát ra hơn 1 vật phẩm (vì vậy không bao giờ có thể ném) .

Nếu không có khí thải và bạn không xử lý rõ ràng (với catchError ) thì lỗi đó sẽ được lan truyền, có thể gây ra sự cố không mong muốn ở một nơi khác và có thể khá khó để theo dõi - đặc biệt là nếu nó đến từ người dùng cuối.

Bạn an toàn hơn khi sử dụng take(1)phần lớn với điều kiện:

  • Bạn ổn với take(1) không phát ra bất cứ thứ gì nếu nguồn hoàn thành mà không có phát xạ.
  • Bạn không cần phải sử dụng một vị từ nội tuyến (ví dụ. first(x => x > 10))

Lưu ý: Bạn có thể sử dụng một vị ngữ take(1)như thế này:.pipe( filter(x => x > 10), take(1) ) . Không có lỗi với điều này nếu không có gì lớn hơn 10.

Thế còn single()

Nếu bạn muốn nghiêm ngặt hơn nữa và không cho phép hai phát thải, bạn có thể sử dụng các single()lỗi đó nếu không có phát thải 0 hoặc 2+ . Một lần nữa, bạn cần xử lý lỗi trong trường hợp đó.

Mẹo: Singleđôi khi có thể hữu ích nếu bạn muốn đảm bảo chuỗi có thể quan sát của mình không thực hiện thêm công việc như gọi một dịch vụ http hai lần và phát ra hai đài quan sát. Thêm singlevào cuối đường ống sẽ cho bạn biết nếu bạn mắc lỗi như vậy. Tôi đang sử dụng nó trong một 'người chạy nhiệm vụ' nơi bạn vượt qua trong một nhiệm vụ có thể quan sát được, chỉ nên phát ra một giá trị, vì vậy tôi chuyển phản hồi thông qua single(), catchError()để đảm bảo hành vi tốt.


Tại sao không luôn luôn sử dụng first()thay vìtake(1) ?

aka Làm thế nào có first khả năng gây ra nhiều lỗi?

Nếu bạn có một thiết bị quan sát có thể lấy thứ gì đó từ một dịch vụ và sau đó chuyển nó qua first()bạn sẽ ổn hầu hết thời gian. Nhưng nếu ai đó đi cùng để vô hiệu hóa dịch vụ vì bất kỳ lý do gì - và thay đổi nó để phát ra of(null)hoặc NEVERsau đó bất kỳ hạ lưufirst() nhà khai thác sẽ bắt đầu ném lỗi.

Bây giờ tôi nhận ra rằng đó có thể là chính xác những gì bạn muốn - do đó đây chỉ là một mẹo. Nhà điều hành firstđã kêu gọi tôi vì nó nghe có vẻ hơi 'vụng về' hơn take(1)nhưng bạn cần cẩn thận trong việc xử lý lỗi nếu có cơ hội nguồn không phát ra. Sẽ hoàn toàn phụ thuộc vào những gì bạn đang làm mặc dù.


Nếu bạn có một giá trị mặc định (không đổi):

Cũng xem xét .pipe(defaultIfEmpty(42), first())nếu bạn có một giá trị mặc định nên được sử dụng nếu không có gì được phát ra. Điều này tất nhiên sẽ không gây ra lỗi vìfirst sẽ luôn nhận được một giá trị.

Lưu ý rằng defaultIfEmptychỉ được kích hoạt nếu luồng trống, không phải nếu giá trị của những gì được phát ra null.


Hãy nhận biết rằng singlecó nhiều sự khác biệt để first. 1. Nó sẽ chỉ phát ra giá trị trên complete. Điều này có nghĩa là nếu quan sát được phát ra một giá trị nhưng không bao giờ hoàn thành thì đơn sẽ không bao giờ phát ra một giá trị. 2. Vì một số lý do nếu bạn chuyển một hàm bộ lọc singlekhông khớp với bất cứ thứ gì, nó sẽ phát ra một undefinedgiá trị nếu chuỗi gốc không trống, điều này không đúng với trường hợp first.
Marinos An

28

Dưới đây là ba quan sát A, BCvới sơ đồ bằng đá cẩm thạch để khám phá sự khác biệt giữa first, takesinglecác nhà khai thác:

so sánh đầu tiên so với các nhà khai thác so sánh

* Chú thích : hoàn thành lỗi
--o-- giá trị
----!
----|

Chơi với nó tại https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Đã có tất cả các câu trả lời, tôi muốn thêm một lời giải thích trực quan hơn

Hy vọng nó sẽ giúp được ai đó


12

Có một sự khác biệt thực sự quan trọng không được đề cập ở bất cứ đâu.

mất (1) phát ra 1, hoàn thành, hủy đăng ký

đầu tiên () phát ra 1, hoàn thành, nhưng không hủy đăng ký.

Điều đó có nghĩa là khả năng quan sát ngược dòng của bạn sẽ vẫn nóng sau lần đầu tiên () có thể không được mong đợi.

CẬP NHẬT: Điều này đề cập đến RxJS 5.2.0. Vấn đề này có thể đã được khắc phục.


Tôi không nghĩ một trong những người hủy đăng ký, xem jsbin.com/nuzulorota/1/edit?js,console .
Weltschmerz

10
Có, cả hai nhà khai thác hoàn thành đăng ký, sự khác biệt xảy ra trong xử lý lỗi. Nếu điều đó có thể quan sát được không phát ra các giá trị và vẫn cố lấy giá trị đầu tiên bằng toán tử đầu tiên, nó sẽ báo lỗi. Nếu chúng ta thay thế nó bằng toán tử Take (1) mặc dù giá trị không có trong luồng khi đăng ký xảy ra, nó không gây ra lỗi.
noelyahan

7
Để làm rõ: cả hai không đăng ký. Ví dụ từ @weltschmerz quá đơn giản, nó không chạy cho đến khi nó có thể tự hủy đăng ký. Cái này được mở rộng hơn một chút: repl.it/repls/FrayedHugeAudacity
Stephan LV

10

Có vẻ như trong RxJS 5.2.0, .first()toán tử có lỗi ,

Do lỗi đó .take(1).first()có thể hoạt động khá khác nhau nếu bạn đang sử dụng chúng vớiswitchMap :

Với take(1)bạn sẽ có được hành vi như mong đợi:

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

Nhưng với .first()bạn sẽ có hành vi sai:

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

Đây là một liên kết đến codepen

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.