Deven Ahluwalia
Deven Ahluwalia

Reputation: 89

Outside promise needs to wait for inner nested promise

I've this usecase wherein I wish to do the following :

  1. Set metadata in indexDb
  2. Iterate over an array of images
  3. See if img is already set in indexDb
  4. if yes, do nothing, if not, download the img
  5. set the downloaded img (as blob) in indexDb
  6. Raise all images processed event at the end

ads data :

[{
ETag:"",
S3URL:"",
duration:30,
filename:"",
linear-gradient:"",
status:"",
timerRequired:"yes"
}]

My code at the moment :

 this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1
      for (let idx in ads) { //Step 2
        this.localforage.getItem(ads[idx]['filename']).then(blob => {
           if(!blob){ //Step 3
             LSPromise = imgSrcToBlob(ads[idx]['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
              return this.localforage.setItem(ads[idx]['filename'], blob); //Step 5
            });
            LSPromises.push(LSPromise);
           }
        });
      }  
    }).then(() => { 
      if(LSPromises.length) {
        Promise.all(LSPromises).then((data) => {
          this.TvLSkeyCount = LSPromises.length;
          this.fireLoadAssetsEvent(); //Step 6
        });
      } 
    });

Problems I am facing :

After the promise for setting metadata is resolved, it straightaway goes to then() block and by that time LSPromises is null. Of course I understand that internal nested promises haven't been resolved yet.

Resolution I tried :

Return LSGetter promises and download images later. this did not work either.

Code I tried :

this.Tvlocalforage.setItem('meta', newMeta).then(() => { 
      for (let idx in ads) {
        let p = this.Tvlocalforage.getItem(ads[idx]['filename']);
        LSPromises.push({'promise' : p, 'filename' : ads[idx]['filename'], 'url' : ads[idx]['S3URL']});
      }
  }).then(() => { 
    if(LSPromises.length){
      Promise.all(LSPromises.map(obj => {
        obj['promise'].then(blob => {
          if(!blob){
              imgSrcToBlob(obj['url'], undefined, 'Anonymous', 1).resolve(blob => {
                return this.Tvlocalforage.setItem(obj['filename'], blob);
            });   
          }
        });
      })).then((data) => {this.fireLoadAssetsEvent();});
    }

I tried 2 more ways to wrapper up and try to return promise.all of download step from inside & on trying to resolve that, return promise.all of set downloaded images to LS. But it did not work.

Upvotes: 1

Views: 151

Answers (3)

charlietfl
charlietfl

Reputation: 171679

Get rid of the for() loop since idx will not be what you want it to be inside the promise callback since loop will complete before promises do

Can use map() instead to create the array using a closure

Something like:

this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1

  let LSPromises = ads.map(ad => {
    return this.localforage.getItem(ads[idx]['filename']).then(blob => {
      if (!blob) { //Step 3
        return imgSrcToBlob(ad['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
          return this.localforage.setItem(ad['filename'], blob); //Step 5
        });
      }
      return null
    });
  });

  return Promise.all(LSPromises).then((data) => {
    this.TvLSkeyCount = data.filter(o => o).length;
    this.fireLoadAssetsEvent(); //Step 6
    // not sure what needs to be returned here
  });

});

Upvotes: 1

trincot
trincot

Reputation: 350167

I could not test this, but you should try to flatten the nesting, chaining the thens at the outermost level. You can use Promise.all even more in order to pass the ad value through the chain together with the resolved values:

this.Tvlocalforage.setItem('meta', newMeta).then(() => // Step 1
    Promise.all(ads.map( ad => // Step 2
        Promise.all(ad, this.localforage.getItem(ad.filename))
    ))
).then(blobs => 
    blobs.filter( ([ad, blob]) => !blob ) // Step 3
).then(blobs =>
    Promise.all(blobs.map( ([ad]) => 
        [ad, imgSrcToBlob(ad.S3URL, undefined, 'Anonymous', 1)] // Step 4
    ))
).then(blobs =>
    Promise.all(blobs.map( ([ad, blob]) =>
        this.localforage.setItem(ad.filename, blob) // Step 5
    ))
).then(data => {
    this.TvLSkeyCount = data.length;
    this.fireLoadAssetsEvent(); // Step 6
});

Upvotes: 0

Günter Zöchbauer
Günter Zöchbauer

Reputation: 657248

There might be other errors, but there is a missing return:

this.Tvlocalforage.setItem('meta', newMeta).then(() => { //Step 1
      for (let idx in ads) { //Step 2
        LSPromises.push(this.localforage.getItem(ads[idx]['filename']).then(blob => {
           if(!blob){ //Step 3
             return /* added return */ imgSrcToBlob(ads[idx]['S3URL'], undefined, 'Anonymous', 1).then((blob) => { //Step 4
              return this.localforage.setItem(ads[idx]['filename'], blob); //Step 5
            });
            // LSPromises.push(LSPromise);
           }
        }));
      }  
     // }).then(() => { 
      if(LSPromises.length) {
        return /* <<<=== */ Promise.all(LSPromises).then((data) => {
          this.TvLSkeyCount = LSPromises.length;
          this.fireLoadAssetsEvent(); //Step 6
        });
      } 
    });

If the promise returned from Promise.all() is not returned, the caller can not wait for it to complete.

Upvotes: 0

Related Questions