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

Kar*_*ban 90 rxjs angular2-observables angular

我发现使用Auth Guards的实现很少AuthGuard.在我的项目中,我曾经take(1)满足我的需求.它的工作方式是否相同?或者其中一个可能有优势.

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

mar*_*tin 135

经营者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));
Run Code Online (Sandbox Code Playgroud)

......以及这个:

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

虽然这将匹配发出的第一个值:

range(1, 5).pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));
Run Code Online (Sandbox Code Playgroud)

另一方面,take(1)只取第一个值并完成.没有进一步的逻辑.

range(1, 5).pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));
Run Code Online (Sandbox Code Playgroud)

然后使用空源Observable它不会发出任何错误:

EMPTY.pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));
Run Code Online (Sandbox Code Playgroud)

2019年1月:更新了RxJS 6

  • @GünterZöchbauer实际上,他们的行为是不同的.如果源没有发出任何东西并完成那么`first()`发送错误通知,而`take(1)`根本不会发出任何东西. (11认同)
  • @Karuban这真的取决于你的用例.如果没有收到任何值是意外的,我建议使用`first()`.如果它是一个有效的应用程序状态,我会选择`take(1)`. (6认同)
  • 请注意,我没有说`first()`和`take()`一般是相同的,我认为这是显而易见的,只有`first()`和`take(1)`是相同的. 我不确定您的回答是否认为仍然存在差异? (3认同)
  • 这类似于.NET的.First()vs..FirstOrDefault()`(并且想到它也是.Take(1)),因为First需要集合中的某些内容,并且为空集合给出错误-以及FirstOrDefault()和.Take(1)都允许集合为空,并分别返回null和Empty集合。 (2认同)

Sim*_*ver 21

提示:仅在以下情况下使用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”与“first”有更多差异。**1.** 它只会在“完整”时发出值。这意味着如果 observable 发出一个值但从未完成,则 single 永远不会发出值。**2.** 由于某种原因,如果您将一个过滤器函数传递给“single”,而该函数与任何内容都不匹配,那么如果原始序列不为空,它将发出“未定义”值,而“first”则不是这种情况。 (2认同)

kos*_*kos 11

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

首次与接受与单人操作比较

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

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

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

希望对别人有帮助


Kam*_*tti 11

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

另一个例子,使用谓词:

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);
Run Code Online (Sandbox Code Playgroud)

作为 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

参考


nor*_*hov 9

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

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

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

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

UPD:这是对RxJS 5.2.0的引用.此问题可能已得到修复.

  • 是的,两个运营商都完成订阅,差异发生在错误处理中.如果该observable不发出值并仍尝试使用第一个运算符取第一个值,则会抛出错误.如果我们用take(1)运算符替换它,即使在订阅发生时流中没有值,它也不会抛出错误. (8认同)
  • 澄清一下:两者都取消订阅.来自@weltschmerz的例子太简单了,直到它可以自行取消订阅才能运行.这个有点扩展:https://repl.it/repls/FrayedHugeAudacity (6认同)

Art*_*tem 9

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

因为这个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...
Run Code Online (Sandbox Code Playgroud)

但是.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... 
Run Code Online (Sandbox Code Playgroud)

这是codepen的链接