Search code examples
angulartypescriptes6-promisechaining

How can I make my second block in the chain trigger?


I have some code for uploading a bunch of photos to a server.

The whole code can be divided into three parts:

  • catching pictures from the input="files" and place them in a veriable files until I call a method uploadPictures().

  • then I am uploading photos and push urls into array so that I can use them later

  • and finally when uploading is over and I have the link I want to update a certain document in the DB (Firestore)

the trouble is that code works well fine for the first two parts. I can make this chain work neither with async/await nor Promises


async uploadPictures() {

    let loop = new Promise( result => {

      this.files.forEach((file, index) => {

        let path: string = `items/${Date.now()}_${this.files[index].name}`;

        let ref = this.storage.ref(path);

        ref.put(file).then(snapshot => {
          snapshot.ref.getDownloadURL().then(downloadLink => {
            this.url.push(downloadLink);
            console.log('Image is uploaded successfully and available at: ' + downloadLink);
          })
        });
      });

    }).then( () => {

      console.log('hello from here!');
      console.log(this.url);
      this.firestore.collection('items').doc(this.itemId).update({ pics: this.url });

    })


  }


I would appreciate any hints or advice!


Solution

  • So I see multiple issues here with how you're doing promises. It's easy to get these wrong if you're new at them, so don't feel bad about it.

    So the first issue is the way you're calling new Promise. This constructor takes a function that has two parameters: resolve and reject. These are functions that, when called, either complete or reject the promise. You are doing neither, so the first promise never completes.

    The second issue you have is that you have promise calls inside promise calls, but you never return the promises back up the call chain, so things don't get chained together properly.

    In your case, I don't think new Promise() is the right construct anyway. I think you need to simply pass out a promise that completes when all the promises you're calling inside the loop finish. So what you need to do is construct an array of all the promises inside the loop, then call Promise.all on them to get a new promise that completes when all the promises in the array are done.

    Also, this whole process would be a lot easier if you just used async/await everywhere instead of explicitly using promises and then calls. :-)

    I think what you want to do would look something like this using explicit promises:

    // Don't need async if not awaiting
    uploadPictures() {
        // Build array of promises for each item in loop.
        // Array.map is an easy way to do this.
        let loop = this.files.map((file, index) => {
            let path: string = `items/${Date.now()}_${this.files[index].name}`;
            let ref = this.storage.ref(path);
    
            // Inner promise - MUST RETURN THIS to chain properly
            // Also, prefer flattened promise chains to nested one
            return ref.put(file)
            .then(snapshot => snapshot.ref.getDownloadURL())
            .then(downloadLink => {
                this.url.push(downloadLink);
                console.log('Image is uploaded successfully and available at: ' + downloadLink);
            });
        });
        return Promise.all(loop)
        .then(() => {
            console.log('hello from here!');
            console.log(this.url);
            return this.firestore.collection('items').doc(this.itemId).update({ pics: this.url });
        });
    }
    

    When using async/await this gets a lot cleaner:

    async uploadPictures() {
        // Build array of promises for each item in loop.
        // Array.map is an easy way to do this.
        let loop = this.files.map(async (file, index) => {
            let path: string = `items/${Date.now()}_${this.files[index].name}`;
            let ref = this.storage.ref(path);
    
            // await lets us avoid explicit returns or chaining
            let snapshot = await ref.put(file);
            let downloadUrl = await snapshot.ref.getDownloadURL();
            this.url.push(downloadLink);
            console.log('Image is uploaded successfully and available at: ' + downloadLink);
        });
    
        await Promise.all(loop);
        console.log('hello from here!');
        console.log(this.url);
        await this.firestore.collection('items').doc(this.itemId).update({ pics: this.url });
    }
    

    Hope this helps!