Company logo
  • Empleos
  • Bootcamp
  • Acerca de nosotros
  • Para profesionales
    • Inicio
    • Empleos
    • Cursos y retos
    • Preguntas
    • Profesores
    • Bootcamp
  • Para empresas
    • Inicio
    • Nuestro proceso
    • Planes
    • Pruebas
    • Nómina
    • Blog
    • Comercial
    • Calculadora

0

76
Vistas
Javascript: How to wait for multiple promises to resolve within a loop before moving forward

I saw similarly asked questions but I am having a hard time applying it to my code.

I have the following piece of code:

  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);
  }

The caller of the first method (createSubmission) should receive a Promise. My code sequence is finilized when we return from the finilize method.

I want to continue after all the elements in the loop have been iterated, and the urlArray has been filled. It is filled when it has the same number of elements as the passed files array, that's why I placed this condition:

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

However, I get an error because the if condition means that Not all code paths return a value. So I believe I should ditch the if condition and wait for all promises to resolve before calling finalize, but I am not very clear on how to do it.

7 months ago · Juan Pablo Isaza
3 Respuestas
Responde la pregunta

0

First of all foreach loop does not return anything, instead, it modifies the existing array.

Now come to the solution

you cannot use forEach when you want your code to wait for some async event. Instead, use a for of loop, and with that use 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);
}

Note: Code snippet may contain syntactical errors

7 months ago · Juan Pablo Isaza Denunciar

0

I don't know what storageRef.getDownloadURL() is from (I assume it is related to Firebase and Angular) so I leave most of your code untouched assuming that it is correct and that you just want to know how to correctly deal with the Promise case.

There are two ways of solving the problem.

  1. Processing the files sequentially using 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. If storageRef.put(file) does some network stuff and you want to parallelize that (which is not always a good idea), you can use Promise.all and Array.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);
}

As you use TypeScript you should add the corresponding type information to my code.

YOu further should check if storageRef.getDownloadURL() provides a Promise API and if so replace new Promise and the subscribe with that one.

7 months ago · Juan Pablo Isaza Denunciar

0

I think what you're looking for is "Promise.all()"

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

Promise.all resolves an array of Promises.

In this example urlArray will contain the resolved values of getDownloadUrl


    let urlArrayTasks = [];

    for(let file of files){
      urlArrayTasks.push(this.getDownloadURL(file));
    }

    let urlArray = await Promise.all(urlArrayTasks);
7 months ago · Juan Pablo Isaza Denunciar
Responde la pregunta
Encuentra empleos remotos