combineLatest 不订阅参数 Observables,不传递数据
combineLatest doesn't subscribe to parameter Observables, doesn't deliver data
我有一个相当常见的用例。我正在从 FirebaseDatabase 加载一个 objects 数组,并从 Firebase 存储附加图像。
FirebaseDatabase returns 基于查询的 Observable。每次数据库中的数据发生变化时,Observable 都会发出更新的查询结果。我还通过为每个条目
映射到 [Domain, ImagePromise] 元组,将存储 api 中的 Promise 附加到结果中的每个条目
getDomainObj(){
return this.af.database.list(this.listRef)
.map((mps: Domain[]) =>
mps.map((mp: Domain) =>
[mp, this.getImage(mp['$key'])]));
}
两期:
- 有些国会议员没有形象。
- 由于图像不在 firedb 中,因此 observable 不会在图像更改时发出。为了解决这个问题,我添加了一个云功能来在每次图像更新时发送推送通知(超出这个问题的范围,它有效,所以不需要深入研究)。每个 mp 都是它自己的 PubSub 主题,我们在客户端订阅该主题,并获得一个 Observable of updates,每次上传特定 mp 的图像时都会发出更新
我通过映射解决了它们
this.mpSvc.getDomainObj()
.map((tupleArray: [Domain, Promise<string>][]) =>
tupleArray.map(([mp, imageSrcPromise]: [Domain, Promise<string>]) => {
return [mp,
Observable.fromPromise(imageSrcPromise) // get initial image
.startWith(null) // make sure that it emits at least once
.concat( // updates
this.mpSvc.signUpForPictureUpdates(mp['$key'])
.map(([id, imageSrc]: [string, string]) => imageSrc))
];
})
)
mpSvc.signUpForPictureUpdates
returns 一个 domainId-imageSrc 元组的 Observable,每次重新上传域图像时都会发出
现在的最终目标是拥有一个 [Domain, imageSrc] 数组的 Observable,它会在每次 Domain object 的任何一个图像发生变化或任何 Domain object发生变化,或者查询结果发生变化。
首先我再次重新映射,而不是发射一个 [Domain,Observable] 对数组,发射一个 Observable<[Domain, imageSrc]>
数组
.map((tupleArray: [Domain, Observable<string>][]) =>
tupleArray.map(([mp, imageSrcObservable]: [Domain, Observable<string>]) =>
imageSrcObservable.map((imageSrc: string) => [mp, imageSrc])))
现在我有一个由成对的 Observable 数组组成的 Observable。 parent Observable 将在每次域或查询结果发生变化时发出,而 children 将在每次特定域的图像发生变化时发出。
最后一步,我将 children observable 组合成一个单独的 observable 和 switchMap 到它。
.switchMap((imageObservables: Observable<[Domain, string]>[]) =>
Observable.combineLatest(imageObservables)
)
结果已正确订阅(事实上,它通过 ngFor|async 在 Angular 模板中显示,但这也在范围之外)。
不幸的是,结果不是预期的那样。实际上,我有四个查询结果 object,两个有图像,两个没有。我看到的并不一致 -- 有时会加载两张图片,有时会加载一张,但大多数情况下两者都不加载。
具体来说,如果我像这样在末尾添加日志行:
.do(x => console.log('final', x), x => console.error('finalerror', x), () => console.log('finalcomplete'));
我总是在 imageSrc 中得到至少一行包含空值的日志行,但几乎没有其他行包含已解析的真实 imageSrc。
在最后一步之前订阅 (combineLatest) 正确获取所有数据
我做错了什么?
编辑:进一步调查后,问题肯定出在 combineLatest
。我尝试用 imageObservables[1].map(x=>[x])
替换 combineLatest
并且它完美地工作(虽然当然只有 returns 一个值而不是数组。此外,单步执行 combineLatest 导致我进行了一些奇怪的关闭,因为什么需要随时关闭吗?
好的,我找到问题了。 fromPromise
如果 Promise 拒绝,将抛出错误。因此,其中一个 children 在 getImage
没有得到图像时出错,这导致 combineLatest
完成。
我这样修的
Observable.fromPromise(imageSrcPromise) // get initial image
.catch(() => Observable.empty()) // <-- fix
.startWith(null) // and so forth...
这就是为什么有时发射正常,有时不发射的原因。如果首先加载图像的域 objects 请求,它们将显示。如果 image-less objects 先返回,流将结束。
我有一个相当常见的用例。我正在从 FirebaseDatabase 加载一个 objects 数组,并从 Firebase 存储附加图像。
FirebaseDatabase returns 基于查询的 Observable。每次数据库中的数据发生变化时,Observable 都会发出更新的查询结果。我还通过为每个条目
映射到 [Domain, ImagePromise] 元组,将存储 api 中的 Promise 附加到结果中的每个条目getDomainObj(){
return this.af.database.list(this.listRef)
.map((mps: Domain[]) =>
mps.map((mp: Domain) =>
[mp, this.getImage(mp['$key'])]));
}
两期:
- 有些国会议员没有形象。
- 由于图像不在 firedb 中,因此 observable 不会在图像更改时发出。为了解决这个问题,我添加了一个云功能来在每次图像更新时发送推送通知(超出这个问题的范围,它有效,所以不需要深入研究)。每个 mp 都是它自己的 PubSub 主题,我们在客户端订阅该主题,并获得一个 Observable of updates,每次上传特定 mp 的图像时都会发出更新
我通过映射解决了它们
this.mpSvc.getDomainObj()
.map((tupleArray: [Domain, Promise<string>][]) =>
tupleArray.map(([mp, imageSrcPromise]: [Domain, Promise<string>]) => {
return [mp,
Observable.fromPromise(imageSrcPromise) // get initial image
.startWith(null) // make sure that it emits at least once
.concat( // updates
this.mpSvc.signUpForPictureUpdates(mp['$key'])
.map(([id, imageSrc]: [string, string]) => imageSrc))
];
})
)
mpSvc.signUpForPictureUpdates
returns 一个 domainId-imageSrc 元组的 Observable,每次重新上传域图像时都会发出
现在的最终目标是拥有一个 [Domain, imageSrc] 数组的 Observable,它会在每次 Domain object 的任何一个图像发生变化或任何 Domain object发生变化,或者查询结果发生变化。
首先我再次重新映射,而不是发射一个 [Domain,Observable] 对数组,发射一个 Observable<[Domain, imageSrc]>
数组.map((tupleArray: [Domain, Observable<string>][]) =>
tupleArray.map(([mp, imageSrcObservable]: [Domain, Observable<string>]) =>
imageSrcObservable.map((imageSrc: string) => [mp, imageSrc])))
现在我有一个由成对的 Observable 数组组成的 Observable。 parent Observable 将在每次域或查询结果发生变化时发出,而 children 将在每次特定域的图像发生变化时发出。
最后一步,我将 children observable 组合成一个单独的 observable 和 switchMap 到它。
.switchMap((imageObservables: Observable<[Domain, string]>[]) =>
Observable.combineLatest(imageObservables)
)
结果已正确订阅(事实上,它通过 ngFor|async 在 Angular 模板中显示,但这也在范围之外)。
不幸的是,结果不是预期的那样。实际上,我有四个查询结果 object,两个有图像,两个没有。我看到的并不一致 -- 有时会加载两张图片,有时会加载一张,但大多数情况下两者都不加载。
具体来说,如果我像这样在末尾添加日志行:
.do(x => console.log('final', x), x => console.error('finalerror', x), () => console.log('finalcomplete'));
我总是在 imageSrc 中得到至少一行包含空值的日志行,但几乎没有其他行包含已解析的真实 imageSrc。
在最后一步之前订阅 (combineLatest) 正确获取所有数据
我做错了什么?
编辑:进一步调查后,问题肯定出在 combineLatest
。我尝试用 imageObservables[1].map(x=>[x])
替换 combineLatest
并且它完美地工作(虽然当然只有 returns 一个值而不是数组。此外,单步执行 combineLatest 导致我进行了一些奇怪的关闭,因为什么需要随时关闭吗?
好的,我找到问题了。 fromPromise
如果 Promise 拒绝,将抛出错误。因此,其中一个 children 在 getImage
没有得到图像时出错,这导致 combineLatest
完成。
我这样修的
Observable.fromPromise(imageSrcPromise) // get initial image
.catch(() => Observable.empty()) // <-- fix
.startWith(null) // and so forth...
这就是为什么有时发射正常,有时不发射的原因。如果首先加载图像的域 objects 请求,它们将显示。如果 image-less objects 先返回,流将结束。