【问题标题】:Javascript: How to wait for multiple promises to resolve within a loop before moving forward [duplicate]Javascript:如何在前进之前等待多个承诺在一个循环中解决[重复]
【发布时间】:2021-10-21 16:14:53
【问题描述】:

我看到了类似的问题,但我很难将其应用到我的代码中。

我有以下代码:

  createSubmission(sub: Submission, files: Files[]): Promise {

    //first add files to storage and create the url array
    let urlArray: string[];
    return files.forEach(file => {
      var storageRef = this.storage.ref(`files/${file.name}`);
      return storageRef.put(file).then(()=> {storageRef.getDownloadURL().subscribe(url => {
        urlArray.push(url);
        if(urlArray.length === files.length){ //we are at the final element
          sub.filesUrls = urlArray;
          return this.finilize(sub);
        }
      })
    });
    });
  }

  finilize(sub: Submission){ //actually creates the submission
    
      let subRef = this.db.database.ref(`/submissions/${sub.medicUid}`).push(); 
      let subKey = subRef.getKey();
      sub.uid = subKey; 
      return subRef.update(sub);
  }

第一个方法的调用者 (createSubmission) 应该收到一个 Promise。当我们从finilize 方法返回时,我的代码序列就完成了。

我想在循环中的所有元素都被迭代并且urlArray 已经被填满之后继续。当它具有与传递的files 数组相同数量的元素时,它被填充,这就是我放置这个条件的原因:

if(urlArray.length === files.length){...}

但是,我收到一个错误,因为 if 条件意味着 并非所有代码路径都返回一个值。 所以我认为我应该放弃 if 条件并等待所有承诺解决在打电话给finalize之前,但我不太清楚该怎么做。

【问题讨论】:

    标签: javascript async-await promise


    【解决方案1】:

    首先,foreach 循环不返回任何内容,而是修改现有数组。

    现在来解决办法

    当您希望代码等待某个异步事件时,您不能使用 forEach。相反,使用 for of 循环,然后使用 await

    async createSubmission(sub: Submission, files: Files[]): Promise {
    
        //first add files to storage and create the url array
        let urlArray: string[];
        for (const file of files) {
            const url = await this.getDownloadURL(file)
            urlArray.push(url)
        }
        sub.filesUrls = urlArray
        return this.finilize(sub)    
    }
    
    getDownloadURL(file):Promise{
        let storageRef = this.storage.ref(`files/${file.name}`);
        return new Promise((resolve, reject) => {
            storageRef.put(file).then(() => {
                storageRef.getDownloadURL().subscribe(url => {
                    resolve(url)
                })
            })
        })
    }
    
    finilize(sub: Submission){ //actually creates the submission
    
        let subRef = this.db.database.ref(`/submissions/${sub.medicUid}`).push();
        let subKey = subRef.getKey();
        sub.uid = subKey;
        return subRef.update(sub);
    }

    注意:代码 sn-p 可能包含语法错误

    【讨论】:

    • new Promise 中的getDownloadURL 是一种反模式,因为其中创建了 Promis 链。并且reject case处理不正确。
    • 代码 sn-p 只是给出一个想法,而不是可以使用的实际代码。你能告诉我更多关于为什么它是反模式的吗?想知道更多。并感谢您提及。
    • 问题是你很容易忘记某些极端情况。这可能会导致承诺链中断、代码未完成或其他意外结果。在您的代码中,您不会考虑 storageRef.put(file)storageRef.getDownloadURL() 导致错误的情况。见What is the explicit promise construction antipattern and how do I avoid it?
    • 谢谢,伙计,非常感谢您的回答。
    【解决方案2】:

    我不知道 storageRef.getDownloadURL() 来自什么(我假设它与 Firebase 和 Angular 有关)所以我保留您的大部分代码不变,假设它是正确的并且您只想知道如何正确处理Promise 案例。

    解决问题的方法有两种。

    1. 使用 await/async 顺序处理文件
    async createSubmission(sub: Submission, files: Files[]): Promise {
    
      //first add files to storage and create the url array
      let urlArray: string[];
      
      for( let file of files ) {
        const storageRef = this.storage.ref(`files/${file.name}`);
        await storageRef.put(file)
        const url = await new Promise((resolve, reject) => {
          try {
             // pass resolve to subscribe. The subscribe will then call resolve with the url
             storageRef.getDownloadURL().subscribe(resolve)
             // you need to check if the callback to passed to subscribe
             // is guaranteed to be called, otherwise you need to
             // handle that case otherwise your code flow will 
             // stuck at this situation
          } catch (err) {
            reject(err)
          }
        })
        urlArray.push(url)
      }
      
      sub.filesUrls = urlArray;
      
      return this.finilize(sub);
    }
    
    finilize(sub: Submission) { //actually creates the submission
    
      let subRef = this.db.database.ref(`/submissions/${sub.medicUid}`).push();
      let subKey = subRef.getKey();
      sub.uid = subKey;
      return subRef.update(sub);
    }
    
    1. 如果storageRef.put(file) 做了一些网络工作,并且您希望并行化(这并不总是一个好主意),您可以使用Promise.allArray.map
    async createSubmission(sub: Submission, files: Files[]): Promise {
    
      //first add files to storage and create the url array
      let urlArray: string[];
    
      urlArray = Promise.all(files.map(async(file) => {
        const storageRef = this.storage.ref(`files/${file.name}`);
        await storageRef.put(file)
        const url = await new Promise((resolve, reject) => {
          try {
            // pass resolve to subscribe. The subscribe will then call resolve with the url
            storageRef.getDownloadURL().subscribe(resolve)
            // you need to check if the callback to passed to subscribe
            // is guaranteed to be called, otherwise you need to
            // handle that case otherwise your code flow will 
            // stuck at this situation
          } catch (err) {
            reject(err)
          }
        })
        
        return url
      }))
    
    
    
      sub.filesUrls = urlArray;
    
      return this.finilize(sub);
    }
    
    finilize(sub: Submission) { //actually creates the submission
    
      let subRef = this.db.database.ref(`/submissions/${sub.medicUid}`).push();
      let subKey = subRef.getKey();
      sub.uid = subKey;
      return subRef.update(sub);
    }
    

    当你使用 TypeScript 时,你应该在我的代码中添加相应的类型信息。

    您应该进一步检查 storageRef.getDownloadURL() 是否提供 Promise API,如果是,则将 new Promisesubscribe 替换为该 API。

    【讨论】:

      【解决方案3】:

      我认为您正在寻找的是“Promise.all()”

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

      Promise.all 解析 Promise 数组。

      在本例中,urlArray 将包含 getDownloadUrl 的解析值

      
          let urlArrayTasks = [];
      
          for(let file of files){
            urlArrayTasks.push(this.getDownloadURL(file));
          }
      
          let urlArray = await Promise.all(urlArrayTasks);
      

      【讨论】:

        【解决方案4】:

        为此,您可以简单地使用Promise.all(),它允许您使用await 来完成多个promise,并将它们的结果放在一个数组中。

        async createSubmission(sub: Submission, files: Files[]): Promise {
            // Creates an array of promises
            let urlArrayPromises = files.map(f => this.getDownloadURL(file));
        
            // await for all the promises to be solved
            let urlArray = await Promise.all(urlArrayPromises);
        
            sub.filesUrls = urlArray;
            return this.finilize(sub);  
        }
        
        
        async getDownloadURL(file) {
            let storageRef = this.storage.ref(`files/${file.name}`);
            // `put` already returns a promise, you can simple await for it
            // there's no need for creating unnecessary callbacks
            await storageRef.put(file);
        
            // returns a promise that will be resolved with the url
            return storageRef.getDownloadURL();
        }
        
        // Creates the submission
        finilize(sub: Submission) {
            let subRef = this.db.database.ref(`/submissions/${sub.medicUid}`).push();
            let subKey = subRef.getKey();
            sub.uid = subKey;
            return subRef.update(sub);
        }
        

        你应该避免使用Promise来返回一个新的promise,如果你要在这个promise中调用的函数已经返回了一个promise。

        确保在 try/catch 块内调用方法 createSubmission 以便正确处理错误。

        假设您使用的是firebase(从代码来看),官方documentationgetDownloadURL返回一个Promise,而您使用subscribe而不是then来得到这个结果方法,但您永远不会关闭该订阅,请注意您的应用程序中的 memory leaks

        【讨论】:

          猜你喜欢
          • 2020-08-28
          • 1970-01-01
          • 2018-03-13
          • 1970-01-01
          • 1970-01-01
          • 2021-08-03
          • 2018-12-14
          • 1970-01-01
          • 2018-03-22
          相关资源
          最近更新 更多