take(1)与first()


137

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

Answers:


197

运算符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更新


2
正如说明,我并没有说first()take()表现一般的一样,我认为这是明显的,只有first()take(1)是相同的。从您的答案中我不确定您是否认为还有区别?
君特Zöchbauer

14
@GünterZöchbauer实际上,他们的行为是不同的。如果源不发出任何东西并完成,则first()发送错误通知,而take(1)根本不发出任何东西。
马丁

@martin,在某些情况下,take(1)不会发出任何表示调试代码会更困难的方法?
卡鲁班'17

7
@Karuban这真的取决于您的用例。如果未收到任何值,则超出我的建议first()。如果它是有效的应用程序状态,我会选择使用take(1)
马丁

2
这类似于.NET的.First()VS .FirstOrDefault()(和来也觉得它.Take(1)在首先需要收集的东西,并给出了一个错误一个空的集合-两者FirstOrDefault().Take(1)允许集合是空的,回报null分别与空集合。
Simon_Weaver

45

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

  • 您认为发出的零项是错误情况(例如,发出前已完成)并且如果出现错误的可能性大于0%,则可以优雅地对其进行处理
  • 或者您知道100%的可观察源将发射1+项(因此永远不会抛出)

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

在大多数情况下,您可以安全使用take(1)

  • take(1)如果源代码完整无发射,则可以不发射任何东西。
  • 您不需要使用内联谓词(例如first(x => x > 10)

注意:可以使用带有take(1)这样的谓词:.pipe( filter(x => x > 10), take(1) )。如果没有大于10的值,则没有错误。

关于什么 single()

如果您想要更严格,并且禁止两次发射,则可以使用零或2+发射时的single()哪些误差。同样,在这种情况下,您需要处理错误。

提示:Single如果您想确保可观察链不会做额外的工作(例如两次调用http服务并发出两个可观察物),可能会很有用。添加single到管道的末尾将使您知道是否犯了这样的错误。我在“任务运行器”中使用它,您在其中传递了一个仅可发出一个值的可观察任务,因此我将响应传递single(), catchError()给它,以确保行为良好。


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

又名 如何first 可能导致更多错误?

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

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


如果您具有默认值(常量):

还考虑.pipe(defaultIfEmpty(42), first())是否具有默认值,如果没有发出任何值,则应使用该默认值。当然,这将不会引发错误,因为first它将始终收到一个值。

请注意,defaultIfEmpty仅在流为空时才触发,而在发出的值是时才触发null


请注意,与single会有更多差异first1.它只会在上发出值complete。这意味着,如果可观察对象发出一个值但从未完成,则single永远不会发出值。2.由于某种原因,如果传递的过滤器函数single不匹配任何内容,则undefined在原始序列不为空的情况下,它将发出一个值,而情况并非如此first
Marinos An

28

这里有三个观测量AB以及C用大理石图表探索之间的差异 firsttake以及single运营商:

首次与接受与单人比较

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

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

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

希望对别人有帮助


12

有一个真正重要的区别,任何地方都没有提到。

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

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

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

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


我认为没有人取消订阅,请参阅jsbin.com/nuzulorota/1/edit?js,console
weltschmerz

10
是的,两个运营商都完成了订阅,区别在于错误处理。如果该可观察的对象未发出值,但仍尝试使用第一个运算符获取第一个值,则会抛出错误。如果我们用take(1)运算符替换它,即使订阅发生时流中没有值,它也不会引发错误。
noelyahan

7
需要说明的是:两者都取消订阅。@weltschmerz中的示例过于简化,它无法运行,直到可以自行取消订阅为止。这个扩展了一点:repl.it/repls/FrayedHugeAudacity
Stephan LV

10

似乎在RxJS 5.2.0中,该.first()运算符存在错误

由于存在该错误.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的链接

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.