如何在链接管道操作后 return 从 angular 中的服务观察到

How to return an observable from service in angular after chaining pipe operations

我正在尝试链接/管道操作和 return 来自 angular 中使用 angular fire 的服务的 Observable。

我保证我有这个工作

服务

saveDiploma(diploma: { title: any; description: any; picture: any }) {
        return new Observable(observer => {
            const id = this.db.createId();
            this.storage.ref(`diplomas/${id}/original.jpg`)
                .putString(diploma.picture, 'data_url')
                .then(task => {
                    task.ref.getDownloadURL()
                        .then(url => {
                            const saved = {
                                title: diploma.title,
                                description: diploma.description,
                                url,
                                createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                                createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
                            };
                            this.db.doc(`diplomas/${id}`)
                                .set(saved)
                                .then(() => {
                                    observer.next(saved);
                                    observer.complete();
                                })
                                .catch(e => observer.error(e));
                        })
                        .catch(e => observer.error(e));
                })
                .catch(e => observer.error(e));
        });
    }

组件

save() {
        this.diplomasService.saveDiploma({
            title: this.diplomaForm.value.title,
            description: this.diplomaForm.value.description,
            picture: this.currentImage
        }).subscribe(diploma => {
            console.log('saved diploma', diploma);
        }, e => console.error('error while saving the diploma', e));
    }

我正在尝试在服务中使用 Observables 而不是 Promises 并像这样通过管道

saveDiploma(diploma: { title: any; description: any; picture: any }) {
        const id = this.db.createId();
        const ref = this.storage.ref(`diplomas/${id}/original.jpg`);
        return ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(
            concatMap(task => {
                console.log('getDownloadURL');
                return from(task.ref.getDownloadURL());
            }),
            concatMap(url => {
                console.log('url', url);
                const saved = {
                    title: diploma.title,
                    description: diploma.description,
                    url,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                    createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
                };
                return from(this.db.doc(`diplomas/${id}`).set(saved));
            })
        );
    }

但是 getDownloadURL 方法在上传完成之前被触发,因此 return 出现错误 storage/object-not-found。我试过在 concatMap(getDownloadURL) 之前添加一个终结器或过滤器(在 task.state == 'success' 上),但我没能让它工作。

有谁知道如何通过管道传输此操作和 return 来自他们的 Observable?

我正在使用 Angular 8.1.2,Angular Fire 5.2.1 和 rxjs 6.5.1

这里的问题是 promise 默认是急切的。我认为用 defer 运算符 (https://rxjs.dev/api/index/function/defer) 包装 from 运算符应该可以解决您的问题。所以代码看起来像这样:

return ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(
            concatMap(task => defer(() => {
                console.log('getDownloadURL');
                return from(task.ref.getDownloadURL());
            })),
            concatMap(url => defer(() => {
                console.log('url', url);
                const saved = {
                    title: diploma.title,
                    description: diploma.description,
                    url,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                    createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
                };
                return from(this.db.doc(`diplomas/${id}`).set(saved));
            }))

传递给 defer 的方法在被订阅后立即被评估。一旦有来自源 observable 的传入通知,ConcatMap 将自动订阅内部 observable。

根据 AngularFire 文档ref.putString(..).snapshotChanges()

Emits the raw UploadTaskSnapshot as the file upload progresses.

所以你的问题是 .snapshotChanges() 在文件上传完成之前发出。 concatMap 在每次从源发出时触发,而不仅仅是在完成时触发。你应该使用 concat.

saveDiploma(diploma: { title: any; description: any; picture: any }) {
  const id = this.db.createId();
  const ref = this.storage.ref(`diplomas/${id}/original.jpg`);
  return concat(
    ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(ignoreElements()),
    defer(() => ref.getDownloadURL().pipe(
      switchMap(url => {
        console.log('url', url);
        const saved = {
          title: diploma.title,
          description: diploma.description,
          url,
          createdAt: firebase.firestore.FieldValue.serverTimestamp(),
          createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
        };
        return this.db.doc(`diplomas/${id}`).set(saved); // you can return a Promise directly
      })
    ))
  );
}

可能的选择:

saveDiploma(diploma: { title: any; description: any; picture: any }) {
  const id = this.db.createId();
  const ref = this.storage.ref(`diplomas/${id}/original.jpg`);
  return ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(
    last(),
    switchMap(() => ref.getDownloadURL()),
    map(url => ({
      title: diploma.title,
      description: diploma.description,
      url,
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
      createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
    })),
    switchMap(saved => this.db.doc(`diplomas/${id}`).set(saved))
  );
}