Angular 2使用RxJS - take(1)vs first()

时间:2017-02-20 13:34:32

标签: angular rxjs rxjs5 angularfire2 angular2-observables

我发现很少使用take(1)的Auth Guards实现。在我的项目中,我使用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)
    }
}

6 个答案:

答案 0 :(得分:104)

运营商first()take()不一样。

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年1月:更新了RxJS 6

答案 1 :(得分:9)

似乎在RxJS 5.2.0中.first()运算符有bug

由于该错误.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

的链接

答案 2 :(得分:9)

提示:仅在以下情况下使用first()

  • 您认为发出的零个项目是错误情况(例如,在发出之前先完成),并且如果您有超过0%的错误机会,请妥善处理它 < / li>
  • OR 您知道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)吗?

aka。 first如何潜在地引起更多错误?

如果您有一个可观察对象,它从服务中获取某些东西,然后将其通过first()进行管道传输,那么大多数时候应该会很好。但是,如果有人出于某种原因来停用该服务-并将其更改为发出of(null)NEVER,则任何下游first()操作员都会开始抛出错误。

现在,我意识到这可能正是您想要的东西-因此,为什么这只是个提示。运算符first之所以吸引我,是因为它听起来比take(1)的“笨拙”要少一些,但是如果有可能源不发光,则需要谨慎处理错误。完全取决于您在做什么。


如果您使用默认值(常量):

还请考虑.pipe(defaultIfEmpty(42), first()),如果您有默认值,则在不发出任何内容时应使用该默认值。当然,这不会引发错误,因为first总是会收到一个值。

请注意,defaultIfEmpty仅在流为空时才触发,而如果发出的值为null则不会触发。

答案 3 :(得分:8)

有一个非常重要的区别,在任何地方都没有提到。

take(1)发出1,完成,取消订阅

first()发出1,完成,但没有取消订阅。

这意味着你的上游observable在first()之后仍然会很热,这可能不是预期的行为。

UPD:这是对RxJS 5.2.0的引用。这个问题可能已经修复。

答案 4 :(得分:2)

以下是三个带有大理石图的可观察对象ABC,以探讨firsttake和{{1}之间的区别}运算符:

first vs take vs single operators comparison

* 传奇
single
--o-- 错误
----! 完成

https://thinkrx.io/rxjs/first-vs-take-vs-single/ 上播放。

已经有了所有答案,我想添加一个更直观的解释

希望它对某人有帮助

答案 5 :(得分:1)

事实证明,这两种方法之间有一个非常重要的区别:如果流在发出值之前完成,则 first()将发出错误。或者,如果您提供了谓词(i.e. first(value => value === 'foo')),则在流通过谓词的值发出之前流完成时,它将发出错误。

另一方面,如果从未从流中发出值,

take(1)将很高兴地继续进行。这是一个简单的示例:

const subject$ = new Subject();

// logs "no elements in sequence" when the subject completes
subject$.first().subscribe(null, (err) => console.log(err.message));

// never does anything
subject$.take(1).subscribe(console.log);

subject$.complete();

另一个示例,使用谓词:

const observable$ = of(1, 2, 3);

// logs "no elements in sequence" when the observable completes
observable$
 .first((value) => value > 5)
 .subscribe(null, (err) => console.log(err.message));

// the above can also be written like this, and will never do
// anything because the filter predicate will never return true
observable$
 .filter((value) => value > 5);
 .take(1)
 .subscribe(console.log);

作为RxJS的新手,这种行为使我非常困惑,尽管这是我自己的错,因为我做出了一些错误的假设。如果我不愿检查文档,就会发现行为是clearly documented

如果未提供defaultValue并且找不到匹配的元素,则会引发错误。

我经常遇到此问题的原因是一个相当普遍的Angular 2模式,其中在OnDestroy生命周期挂钩期间手动清除了可观察对象:

class MyComponent implements OnInit, OnDestroy {
  private stream$: Subject = someDelayedStream();
  private destroy$ = new Subject();

  ngOnInit() {
    this.stream$
      .takeUntil(this.destroy$)
      .first()
      .subscribe(doSomething);
  }

  ngOnDestroy() {
    this.destroy$.next(true);
  }
}

该代码起初看起来是无害的,但是当stream$之前被销毁的组件可以发出值时,就会出现问题。因为我使用的是first(),所以销毁组件时会引发错误。我通常只订阅流以获取要在组件内使用的值,因此我不在乎该组件在流发出之前是否已被销毁。因此,我几乎在以前使用过take(1)的所有地方都开始使用first()

filter(fn).take(1)first(fn)更加冗长,但是在大多数情况下,我更喜欢冗长而不是处理最终不会对应用程序造成影响的错误。

还要注意的一点:last()takeLast(1)也是一样。

reference