我们应该避免嵌套的 rxjs 运算符吗?一个我无法测试的案例
Should we avoid nested rxjs operators? One case which I cannot test
我在使用 rxjs
的 Angular 应用程序中编写了以下效果。在 MyActions.myAction
,我收到一个包含 属性 ids
的对象 - 一个 ID 数组 - 对于每个 ID,我想通过 this.myApiService.getResource
发送一个 HTTP 请求,returns一个Observable<Resource>
。然后我想将所有结果收集到一个数组中,并分派另一个传递该数组的操作。
public loadResources$: Observable<MyAction> = this.actions$.pipe(
ofType(MyActions.myAction),
switchMap(({ ids }) => from(ids).pipe(
mergeMap(id => this.myApiService.getResource(id)),
toArray()
)),
map(resources) => MyActions.resourcesLoaded({ resources } )),
);
上面的代码完成了这项工作,但我想知道我是否应该避免嵌套两个响应式运算符流,以及是否有更好的编写方法。
我想知道的原因是我在为它编写测试时遇到问题。我在下面写了测试,但我不能让它通过。
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------t-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
我得到的错误是:
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
Error: Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
at <Jasmine>
at compare (http://localhost:9876/Users/jacopolanzoni/Documents/Development/myProject/node_modules/jasmine-marbles/index.js:91:1)
at <Jasmine>
我发现我的测试失败了,因为 toArray
正在等待 getResource
(即 httpClient.get
)返回的 observable 完成。将 t
替换为 (t|)
修复了测试:
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------(t|)-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
然而,我的问题的第一部分,即像这样嵌套运算符是否是好的做法,仍然有效。
but I wonder whether I should avoid nesting two flows of reactive operators, and whether there is a better way to write that
我会说这取决于你想要实现的目标,至少在这种情况下是这样。
of([1,2,3]).pipe(mergeAll(), switchMap(value => http.get(...)))
不同于
of([1,2,3]).pipe(switchMap(ids => from(ids).pipe(mergeMap(...))))
在第一种情况下,每个内部 observable 将被下一个值丢弃(最后一个值除外),因此只有 3 个会解析。
在第二种情况下,它将处理所有这些,因为您在内部可观察对象中分解数组(由 swtichMap
管理,因此丢弃其内部可观察对象的唯一方法是如果一个新的外部值(例如另一个 id 数组)由源发出)。
不需要嵌套的情况是:
of([1,2,3])
.pipe(
// whenever you want to explode an array,
// it does not matter which higher order operator you use
// since the operation is **synchronous**
// so, `mergeAll`, `concatAll`, `switchAll` should work the same
mergeAll(),
mergeAll(id => this.apiService.useId(id))
)
// same as
of([1,2,3])
.pipe(
mergeMap(ids => from(ids).pipe(mergeMap(id => this.apiService.useId(id))))
)
如您所见,在本例中,switchMap
已替换为 mergeMap
。
我在使用 rxjs
的 Angular 应用程序中编写了以下效果。在 MyActions.myAction
,我收到一个包含 属性 ids
的对象 - 一个 ID 数组 - 对于每个 ID,我想通过 this.myApiService.getResource
发送一个 HTTP 请求,returns一个Observable<Resource>
。然后我想将所有结果收集到一个数组中,并分派另一个传递该数组的操作。
public loadResources$: Observable<MyAction> = this.actions$.pipe(
ofType(MyActions.myAction),
switchMap(({ ids }) => from(ids).pipe(
mergeMap(id => this.myApiService.getResource(id)),
toArray()
)),
map(resources) => MyActions.resourcesLoaded({ resources } )),
);
上面的代码完成了这项工作,但我想知道我是否应该避免嵌套两个响应式运算符流,以及是否有更好的编写方法。
我想知道的原因是我在为它编写测试时遇到问题。我在下面写了测试,但我不能让它通过。
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------t-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
我得到的错误是:
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
Error: Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
at <Jasmine>
at compare (http://localhost:9876/Users/jacopolanzoni/Documents/Development/myProject/node_modules/jasmine-marbles/index.js:91:1)
at <Jasmine>
我发现我的测试失败了,因为 toArray
正在等待 getResource
(即 httpClient.get
)返回的 observable 完成。将 t
替换为 (t|)
修复了测试:
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------(t|)-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
然而,我的问题的第一部分,即像这样嵌套运算符是否是好的做法,仍然有效。
but I wonder whether I should avoid nesting two flows of reactive operators, and whether there is a better way to write that
我会说这取决于你想要实现的目标,至少在这种情况下是这样。
of([1,2,3]).pipe(mergeAll(), switchMap(value => http.get(...)))
不同于
of([1,2,3]).pipe(switchMap(ids => from(ids).pipe(mergeMap(...))))
在第一种情况下,每个内部 observable 将被下一个值丢弃(最后一个值除外),因此只有 3 个会解析。
在第二种情况下,它将处理所有这些,因为您在内部可观察对象中分解数组(由 swtichMap
管理,因此丢弃其内部可观察对象的唯一方法是如果一个新的外部值(例如另一个 id 数组)由源发出)。
不需要嵌套的情况是:
of([1,2,3])
.pipe(
// whenever you want to explode an array,
// it does not matter which higher order operator you use
// since the operation is **synchronous**
// so, `mergeAll`, `concatAll`, `switchAll` should work the same
mergeAll(),
mergeAll(id => this.apiService.useId(id))
)
// same as
of([1,2,3])
.pipe(
mergeMap(ids => from(ids).pipe(mergeMap(id => this.apiService.useId(id))))
)
如您所见,在本例中,switchMap
已替换为 mergeMap
。