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
则不会触发。
这里用弹珠图A
、B
、C
三个Observable来探究first
、take
、[=之间的区别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)
。
我发现了一些使用 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
则不会触发。
这里用弹珠图A
、B
、C
三个Observable来探究first
、take
、[=之间的区别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)
。