0

I need to implement the following logic.

I need to download an image, but sometime it happens that a downloaded file is corrupted, so I need to try to download it again. Here is how my download promise looks like.

return Downloader.downloadImage(downloadUrl, fileName).then((filename) => {
            // Update some info, save ... and return new promise
            return doStuffAndReturnPromise(filename);
        });

But as I've described above I need to verify if the downloaded file is valid image and only then return promise with success.

Here is a skeleton.

 return new Promise(function (resolve, reject) {
            let retryCounter = MyService.RETRY_COUNT;
            let success = false;
            while (retryCounter > 0 && !success) {
                 // Run new promise but wait until it completes
                 // Decrease the counter
                retryCounter--;
            }
        });

The problem here is that I need to run the download promise synchronously and only then continue iterating and running a new promise.

Please suggest the best way to solve this problem elegantly.

Thanks.

3 Answers3

0

You can just return a Promise that try again instead of the answear, something like this:

var maxAttempts = 5
var counter = 0

return Downloader.downloadImage(downloadUrl, fileName).then((filename) => {
    // Update some info, save ... and return new promise
    if (checkIfNeedToTryAgain()) {
        counter++;
        if (counter > maxAttempts) {
            throw maxAttemptsError
        }
        // Try again
        return Downloader.downloadImage(downloadUrl, fileName)
    }
    // In case of success
    return doStuffAndReturnPromise(filename);
}); 

You could use it like this:

Downloader.downloadImage(downloadUrl, fileName)
.then(function(data) {
    // Here your file is right
})
.catch(function(err) {
    // Here you tried 5 times and failed
})
Denis
  • 2,030
  • 1
  • 12
  • 15
0

I wrote this example a while back (http://jsbin.com/xefutos/6/edit?html,js,output) that you should be able to adapt for your own needs:

// Function that returns a promise
var searchForNumber = function(number) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        var min = 1;
        var max = 10;
        var val = Math.floor(Math.random()*(max-min+1)+min);

        console.log('Value is: ' + val.toString());        

        return resolve(val);        
      }, 1000);
    });
};

// fn     : function that should return a promise.
// args   : the arguments that should be passed to fn.
// donefn : function that should check the result of the promise
//    and return true to indicate whether ploop should stop or not.
// promise: A promise value that is used internally by ploop and should never 
//    be passed in by the caller of ploop.
var ploop = function(fn, args, donefn, promise) {
    return (promise = promise || Promise.resolve(true))    
      .then(function() {
          return(fn.apply(null, args));
      })
      .then(function(result) {
        var finished = donefn(result);
        if(finished === true){
           return result;
        } else {
          return ploop(fn, args, donefn, promise);
        }
    });
};

var searchFor = 4;

var donefn = function(result) {
  return result == searchFor;
};

console.log('Searching for: ' + searchFor);
ploop(searchForNumber, [searchFor], donefn)
  .then(function(val) {
    console.log('Finally found! ' + val.toString());  
    process.exit(0);
  })
  .catch(function(err) {
    process.exit(1);
  });
Mike Cheel
  • 12,626
  • 10
  • 72
  • 101
0

I find Observer pattern to be really helpful in these kind of scenarios as it makes code more readable and reusable. Here is what the code could look like

// MyObservable.js
var util = require('util');
var EventEmitter = require('events').EventEmitter;

function MyObservable() {
  EventEmitter.call(this);
}

MyObservable.prototype.downloadImage = function(imageUrl) {
  // download image code and validate
  if (imageIsValid) {
    this.emit('done', 'valid image');
  } else {
    this.emit('error', 'invalid image')
  }
}

Above is the code for download. IMO, Its more reusable into the future as well or have different observers to take action on it.

const MyObservable = require('./MyObservable');

const observable = new MyObservable();


let retryCount = 3;
observable.on('done', (message) => {
  console.log('Download completed');
});
observable.on('error', (message) => {
  retryCount -= 1;
  if (retryCount > 0) {
    observable.downloadImage(imageUrl);
  }
});
observable.downloadImage(imageUrl);
AbhinavD
  • 6,892
  • 5
  • 30
  • 40