take(1) 与 first()

take(1) vs first()

我发现了一些使用 take(1)AuthGuard 的实现。在我的项目中,我使用了 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() 运算符采用可选的 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 更新

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

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

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

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

更新:这是指 RxJS 5.2.0。此问题可能已经解决。

似乎在 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... 

这是 link 到 codepen

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

  • 您认为发出零个项目是错误条件(例如,在发出前完成) 并且 如果您处理错误的可能性大于 0%它优雅地
  • OR 你 100% 知道源 observable 将发出 1+ 个项目(因此永远不会抛出).

如果零排放并且您没有明确处理它(使用 catchError),那么该错误将向上传播,可能会在其他地方导致意外问题并且可能很难追踪 - 特别是如果它来自最终用户。

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

  • 如果源在没有发射的情况下完成,您可以 take(1) 不发射任何东西。
  • 您不需要使用内联谓词(例如 first(x => x > 10)

注意:您 可以 使用带有 take(1) 的谓词,如下所示:.pipe( filter(x => x > 10), take(1) )。如果没有任何东西大于 10,那么这没有错误。

single()

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

提示:如果您想确保您的可观察链不做额外的工作,例如两次调用 http 服务并发出两个可观察对象,Single 有时会很有用。如果您犯了这样的错误,将 single 添加到管道的末尾会让您知道。我在 'task runner' 中使用它,您在其中传递一个应该只发出一个值的任务可观察对象,因此我通过 single(), catchError() 传递响应以保证良好的行为。


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

又名。 first 怎么会 导致更多错误?

如果您有一个从服务中获取某些内容然后将其通过管道传输到 first() 的可观察对象,您在大多数情况下应该没问题。但是,如果有人出于某种原因来禁用该服务 - 并将其更改为发出 of(null)NEVER,那么任何下游 first() 操作员都会开始抛出错误。

现在我意识到这可能正是您想要的 - 因此这只是一个提示。运算符 first 对我很有吸引力,因为它听起来 'clumsy' 比 take(1) 稍微小一点,但如果源有可能不发射,您需要小心处理错误。将完全取决于你在做什么。


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

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

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

这里用弹珠图ABC三个Observable来探究firsttake、[=之间的区别15=] 运算符:

* 图例:
--o--
----!错误
----|完成

https://thinkrx.io/rxjs/first-vs-take-vs-single/ 玩一下。

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

希望对大家有所帮助

事实证明,这两种方法之间有一个非常重要的区别:如果流在发出值之前完成,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:

Throws an error if defaultValue was not provided and a matching element is not found.

我 运行 如此频繁地参与其中的原因是一个相当常见的 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(),当组件被销毁时会抛出一个错误。我通常只订阅一个流来获取要在组件内使用的值,所以我不关心组件是否在流发出之前被销毁。因此,我开始在几乎所有以前使用 first().

的地方使用 take(1)

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

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

reference