我发现了一些使用take(1)的AuthGuards实现。在我的项目中,我使用了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)
    }
}

操作符first()和take(1)是不同的。

first()操作符接受一个可选的谓词函数,并在源完成时没有匹配值时发出错误通知。

例如,这将发出一个错误:

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更新


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

Take(1)生成1,完成,取消订阅

First()触发1,完成,但不取消订阅。

这意味着你的上游可观察对象在first()之后仍然是热的,这可能不是预期的行为。

UPD:这是指RxJS 5.2.0。这个问题可能已经解决了。


在RxJS 5.2.0中,.first()操作符似乎有一个错误,

因为这个错误,如果你在switchMap中使用.take(1)和.first(),它们的行为会有很大的不同:

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

这是codeen的链接


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

您认为发出的零项是错误条件(例如。如果出错的几率大于0%,你就优雅地处理它 或者你100%知道源可观察对象将发射1+项(所以永远不会抛出)。

如果零排放,并且您没有显式地处理它(使用catchError),那么错误将被传播,可能会在其他地方引起意外的问题,并且可能相当棘手—特别是如果它来自最终用户。

在大多数情况下,使用take(1)会更安全,前提是:

如果源完成时没有发射,那么take(1)不发射任何东西也没问题。 您不需要使用内联谓词(例如。第一个(x => x > 10))

注意:您可以像这样使用take(1)的谓词:.pipe(filter(x => x > 10), take(1))。如果没有比10更大的数,这就没有错误。

那么single()呢?

如果你想更严格,不允许两次释放,你可以使用single(),如果有0或2+释放,它会出错。同样,在这种情况下,您需要处理错误。

提示:如果你想确保你的可观察对象链没有做额外的工作,比如调用http服务两次并发出两个可观察对象,那么Single偶尔会有用。将single添加到管道的末尾将让您知道是否犯了这样的错误。我在一个“任务运行器”中使用它,在那里你传递一个任务可观察到的,应该只发出一个值,所以我通过single(), catchError()传递响应,以保证良好的行为。


为什么不总是使用first()而不是take(1) ?

又名。首先如何可能导致更多错误?

如果你有一个可观察对象,它从服务中获取一些东西,然后通过管道first()传递它,那么大多数时候应该没问题。但是如果有人出于某种原因禁用了该服务——并将其更改为发出(null)或NEVER,那么任何下游的first()操作符都会开始抛出错误。

现在我意识到这可能正是你想要的——因此这只是一个技巧。操作符首先吸引了我,因为它听起来没有take(1)那么“笨拙”,但如果有可能源不发射,你需要小心处理错误。这完全取决于你在做什么。


如果你有一个默认值(常量):

如果您有一个默认值,则在没有触发时使用该值,那么还要考虑.pipe(defaultIfEmpty(42), first())。这当然不会引发错误,因为first总是会接收到一个值。

注意,只有在流为空时才会触发defaultIfEmpty,而不是在发出的值为空时才会触发。


以下是三个可观察对象A、B和C,并带有大理石图,以探索first、take和单个操作符之间的区别:

*传奇: —o—价值 ——!错误 ——|完成

在https://thinkrx.io/rxjs/first-vs-take-vs-single/上玩吧。

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

希望它能帮助到别人


事实证明,这两个方法之间有一个非常重要的区别:如果流在发出值之前完成,first()将发出一个错误。或者,如果你已经提供了一个谓词(即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的新手,这种行为让我非常困惑,尽管这是我自己的错,因为我做了一些不正确的假设。如果我费心去检查文档,我就会看到这种行为被清楚地记录下来:

如果未提供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);
  }
}

代码一开始看起来无害,但当组件在流$发出值之前被销毁时,问题就出现了。因为我使用的是first(),所以当组件被销毁时会抛出一个错误。我通常只订阅流以获得将在组件中使用的值,所以我不关心组件是否在流发出之前被销毁。正因为如此,我开始在几乎所有以前使用take(1)的地方使用take(1)。

filter(fn).take(1)比first(fn)更冗长,但在大多数情况下,我更喜欢处理一些最终对应用程序没有影响的错误。

同样需要注意的是:这同样适用于last()和takeLast(1)。

参考