Search code examples
javascripttypescriptrecursionionic-frameworkes6-promise

Recursively call promises


I've been scouring the web over this one for quite some time now.

I'm prototyping an Angular service for an Ionic app. The purpose of this service is to download an image. Now this is a problem that, in standard JS, I'd like to solve with some recursive calls to avoid duplicate code.

I've tried writing it using promises to get my feet wet with the concept of Promises and it's giving me a hard time.

Consider the following code:

public getBgForName = (name: string) => {
  name = name.toLowerCase();
  var instance = this;
  var dir = this.file.dataDirectory;
  return new Promise(function (fulfill, reject) {
    instance.file.checkDir(dir, name).then(() => {
      // directory exists. Is there a bg file?
      dir = dir + '/' + name + '/';
      instance.file.checkFile(dir, 'bg.jpg').then(() => {
        console.log('read file');
          fulfill(dir + '/' + 'bg.jpg')
      }, (err) => {
        // dl file and re-call
        console.log('needs to download file!')
        instance.transfer.create().download(encodeURI('https://host.tld/'+name+'/bg.jpg'), dir + 'bg.jpg', true, {})
          .then((data) => {
            return instance.getBgForName(name).then((url) => {return url});
          }, (err) => {
            console.log(err)
          })
      })
    }, (err) => {
      // create dir and re-call
      instance.file.createDir(dir, name, true).then(() => {
          instance.getBgForName(name).then((url) => {fulfill(url)});
      })
    })

  });
}

the promise, when called - never quite fully resolves. I think, after reading this article that the problem lies in my the promise resolving not being passed correctly to the "original" promise chain - so that it resolves to solve level, but not all the way to the top. This is supported by the promise resolving correctly when the following is assured:

  • the directory has already been created

  • the file has already been downloaded

so I reckon the return statements somehow break up the link here, leading to the promise not being resolved after it's first recursive call.

What is the correct way to call a promise recursively, ensuring the the original caller receives the result when it is ready?

Edit: Outlining the desired result, as suggested by David B. What the code is supposed to be is the function that is called on a list of items. For each item, there is a background image available, which is stored on a server. This background image will be cached locally. The goal of using recursively calls here is that no matter the state (downloaded, not downloaded) the function call will always return an url to the image on the local filesystem. The steps for this are as follows:

  • create a directory for the current item
  • download the file to this directory
  • return a local URL to the downloaded file

subsequent calls thereafter will only return the image straight from disk (after checking that it exists), with no more downloading.


Solution

  • After reading about the benefits of async / await over promises (and falling in love with the cleaner syntax) I rewrote it using async / await. The refactored (but not perfect!) code looks like this:

    public getBgForName = async (name: string) => {
      name = name.toLowerCase();
      let instance = this;
      let dir = this.file.dataDirectory;
    
      try{
        await instance.file.checkDir(dir, name)
        dir = dir + name + '/';
        try{
          await instance.file.checkFile(dir, 'bg.jpg')
          return dir + 'bg.jpg';
        }catch(err) {
          // download file
          await instance.transfer.create().download(encodeURI('https://host.tld/'+name+'/bg.jpg'), dir + 'bg.jpg', true, {})
          return this.getBgForName(name);
        }
      }catch(err) {
        // not catching the error here since if we can't write to the app's local storage something is very off anyway.
        await instance.file.createDir(dir, name, true)
        return this.getBgForName(name);
      }
    }
    

    and works as intended.