【问题标题】:uploading asynchronous incoming images to Firebase storage and awaiting for all the URLs to be returned将异步传入图像上传到 Firebase 存储并等待所有要返回的 URL
【发布时间】:2019-11-08 16:29:46
【问题描述】:

所以我是一个 Angular 应用程序,它允许您将一些内容上传到 Cloud Storage。比如说,标题、描述和一些图像。我将图像存储在 firebase 存储中,然后也将下载 URL 添加到 firebase。我正在构建一个接收文件数组的方法,将图像裁剪为更可用的大小,然后将这些文件上传到 Firebase 存储:

  uploadImages(images, title) {
    const urls: string[] = [];
    this.ng2ImgToolsService.resize(images, 700, 400).subscribe(result => {
      const uploadImage = new File([result], result.name);

      const path = `${title}/${Date.now()}_${uploadImage.name}`;
      const ref = this.storage.ref(path);
      const task = this.storage.upload(path, uploadImage);

      task.snapshotChanges().pipe(tap(), finalize(async () => {
        const url = await ref.getDownloadURL().toPromise;
        urls.push(url);
      }));  

    }, error => {
      console.log('resize failed, returning original images: ' + error)
    });

    return urls;
  }

这显然行不通。我还尝试将所有内容都包装在一个承诺中,这样我就可以做到;

const urls = await this.uploadImages(...);

但在这一点上,我在 promise 中的 observables 中有 observables,我必须承认我已经过头了。在我处理异步任务的时候,它们更加直截了当。

我又花了几天时间阅读 RxJs,但恐怕我自己无法弄清楚。

总而言之,这个函数需要返回一个url数组,我需要能够等待这个数组,所以基本上等待上传所有内容到firestore,直到我有下载url。

编辑 好吧,在玩了 dockeryxk 答案之后,我仍然遇到了一些问题。首先,“ng2ImgToolsService.resize(images, 700, 400)”函数接收一个图像数组,并返回一个承诺,一旦完成,将通过每个裁剪的图像,而不是一次所有的图像.因此,如果我尝试将其包装在一个 Promise 中并解析此 observable 的输出,它将仅返回一个图像(以先裁剪的为准)。所以我必须等待 observable 完成发射数据?这可能吗?

其次,如果我确实拆分了所有内容,以便在拥有一组裁剪图像后,我会将这些图像放入一个新函数中。在该函数中,我必须遍历该数组并一一上传。

 uploadImages2(images, title) {
    const observables = [];

    for (let image of images) {
      const path = `${title}/${Date.now()}_${image.name}`;
      const ref = this.storage.ref(path);
      const task = this.storage.upload(path, image);

      task.snapshotChanges().pipe(tap(), finalize(async () => {
        const url =  ref.getDownloadURL();
        observables.push(url);
      }));
    }

    return new Promise((resolve, reject) => {
      Observable.combineLatest(observables).subscribe(
        responses => {
          resolve(responses);
        },
        error => {
          console.log('error:', error);
          reject(error);
        }
      );
    });
  }

这确实会正确上传图像,但不会返回下载 url 数组。它基本上冻结在承诺的某个地方。

【问题讨论】:

    标签: angular typescript firebase asynchronous rxjs


    【解决方案1】:

    由于您对 Promise 更满意,因此您可以做一些事情,例如制作一组您的 observables,然后将它们组合起来。

    const observables = [
      ...
    ];
    
    return new Promise((resolve, reject) => {
      Observable.combineLatest(observables).subscribe(
        responses => {
          // responses array will be in the same order as requests
          console.log('responses:', responses);
          resolve(responses);
        },
        error => {
          console.log('error:', error);
          reject(error);
        }
      );
    });
    

    编辑:我没有意识到这个包是如何工作的,试试这样的:

    uploadImages(images, title) {
      const urls: string[] = [];
      return new Promise((resolve, reject) => {
        this.ng2ImgToolsService.resize(images, 700, 400).subscribe(result => {
          const uploadImage = new File([result], result.name);
    
          const path = `${ title }/${ Date.now() }_${ uploadImage.name }`;
          const ref = this.storage.ref(path);
          const task = this.storage.upload(path, uploadImage);
    
          task.snapshotChanges().pipe(tap(), finalize(async () => {
            const url = await ref.getDownloadURL().toPromise();
            urls.push(url);
            // in this case, all images should be done
            if (urls.length === images.length) {
              resolve(urls);
            }
          }));
    
        }, error => {
          console.log('resize failed, returning original images: ' + error);
          reject(error);
        });
      });
    }
    

    【讨论】:

    • 好吧,但是 resize() observable 返回一个文件数组,然后对于每个文件,我分别将它们上传到 Firebase 存储,然后返回另一个带有下载 url 的 observable。那么,最好将其拆分为单独的函数并执行两次您的方法,还是有更好的方法?
    • 就个人而言,出于可读性目的,我会将其拆分为函数。另一种选择是使用主题。基本上,你可以留下你所拥有的,在 urls.push(url) 之后,你可以调用 behaviorSubjectVariableName.next(urls)。您可以在其他地方订阅该主题并从那里继续。
    • 简单但非常聪明的解决方案,谢谢。我意识到我的错误一定是别的东西,它确实是正确地裁剪和上传文件。只是“const url = await ref.getDownloadURL().toPromise();”从不返回 URL。所以承诺永远不会解决。
    • toPromise 已被弃用,所以这可能就是原因。您也可以将此解决方案用于该部分。祝你好运!
    【解决方案2】:

    好吧,我想通了。非常感谢 dockleryxk 提供了一个通用的解决方案。拆分我的功能并基本上两次执行 dockeryxk' 策略最终奏效了:

    调整传入图像大小的功能:

    resizeImages(images: File[], width: number, height: number) {
    
        /*
        this function takes in an array of images, a width and a height.
        if the array contains 'png' or 'jpg' files. it will scale down the images to either the width or the height given.
        if the array contains other image files it will just return them.
        */
    
        const toResize: File[] = [];
        const resized: File[] = [];
    
        for (const file of images) {
          if (this.getFileExtension(file) === 'png' || this.getFileExtension(file) === 'jpg') {
            toResize.push(file);
          } else {
            resized.push(file);
          }
        }
    
        return new Promise((resolve, reject) => {
          if (toResize.length > 0) {
            this.ng2ImgToolsService.resize(toResize, width, height).subscribe(response => {
              resized.push(this.blobToFile(response, response.name));
    
              if (resized.length === images.length) {
                resolve(resized);
              }
            }, error => {
              console.log('error:', error);
              reject(error);
            });
          } else {
            resolve(resized);
          }
        });
      }
    

    上传这些图片的功能:

      uploadImages(images: File[], title: string) {
        return new Promise((resolve, reject) => {
          const urls: string[] = [];
          for (const file of images) {
            const path = `${title}/${Date.now()}_${file.name}`;
            const ref = this.storage.ref(path);
            const task = this.storage.upload(path, file);
    
            task.snapshotChanges().pipe(
              finalize(() => {
                ref.getDownloadURL().subscribe(url => {
                  urls.push(url);
    
                  if (images.length === urls.length) {
                    resolve(urls);
                  }
                });
              })
            ).subscribe();
          }
        });
      }
    

    然后在你的代码中使用它们:

    const imgs: any = await this.resizeImages(data.images, 700, 400);
    console.log(imgs);
    const urls: any = await this.uploadImages(imgs, data.title);
    console.log(urls);
    

    我可以说这不是最好的解决方案,我等待的时间太长了,这可能会加快速度。因此,如果有人有更好的解决方案,我很想听听。但这行得通!

    【讨论】:

      猜你喜欢
      • 2021-04-28
      • 2018-12-21
      • 2019-09-12
      • 2021-10-14
      • 1970-01-01
      • 2019-03-24
      • 2017-09-25
      相关资源
      最近更新 更多