首页 > 解决方案 > 角度可观察管道返回数组

问题描述

我对 Angular 9 相当陌生,基本上我想知道如何从异步源返回一组对象?我目前有一个 firestore 查询,其中返回的文档具有我希望呈现其数据的引用。

ngOnInit

this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(doc => {
                    const val = doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            })).subscribe(val => {
                this.links = val;
        });

HTML

<ion-item *ngFor="let link of links | async">
            <ion-avatar slot="start">
                <img [src]="getImage(link.get('profilepic'))">
            </ion-avatar>
            <ion-label>
                <h2>{{link.get('name')}}</h2>
                <p>{{link.get('bio')}}</p>
            </ion-label>
            <ion-checkbox id="{{link.id}}"></ion-checkbox>
        </ion-item>

这当前返回错误: InvalidPipeArgument: '[object Promise]' for pipe 'AsyncPipe' 当我在 HTML 中删除异步管道时,它返回 zoneawarepromise。

编辑

this.links = this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(async doc => {
                    const val = await doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            }));

这现在可行,但是由于数组,我不知道该解决方案对于大量文档的效率和可扩展性 ret注意:querySnap 是一个对象,它具有内置的 forEach 方法来遍历其docs属性中的数组。

标签: angulargoogle-cloud-firestorerxjsobservableangular9

解决方案


通常不建议混合使用 Promise 和 observables。它们确实可以很好地配合使用,但它会混淆并混淆您的代码。将 promise 转换为 observable 也非常简单。大多数创建/处理高阶 observables 的操作符将为您进行转换。否则,from(promise)也返回一个 observable。

使用 forkJoin

在这种情况下,forkJoin接受一系列Promise毫无怨言的。

forkJoin将同时运行所有承诺并返回一个响应数组一次/如果它们都完成/解决。

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  mergeMap(querySnap => forkJoin(
    querySnap.docs.map(
      doc => doc.get('otherUser').get()
    ))
  )
);

使用连接

concat也可以接受 Promise 作为参数。我们使用扩展运算符 (...) 将数组转换为参数列表。

这实际上更接近于您通过 Promise 实现的目标,因为您await在运行下一个 Promise 之前每个 Promise。这将一次运行你的承诺(而不是同时运行,方式forkJoin

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  mergeMap(querySnap => concat(
    ...querySnap.docs.map(
      doc => doc.get('otherUser').get()
    ))
  ),
  toArray()
);

旁白:清理你的承诺代码

这个:

this.links = this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(async doc => {
                    const val = await doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            }));

相当于

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  map(querySnap => {
    const ret = [];
    querySnap.forEach(async doc => 
      ret.push(await doc.get('otherUser').get())
    );
    return ret;
  })
)

基本上,如果你使用 await,你不需要.then(...


推荐阅读