Javascript: Function that retries with setTimeout

12,097

Solution 1

I've got two ideas:

Move the promise out side of the iterated function downloadItemWithRetryAndTimeout - now resolve() will available to all iterations:

function downloadWrapper(url, retry) {
    return new Promise(function (resolve, reject) {
        function downloadItemWithRetryAndTimeout(url, retry, failedReason) {

            try {
                if (retry < 0 && failedReason != null)
                    reject(failedReason);

                downloadItem(url);
                resolve();
            } catch (e) {
                setTimeout(function () {
                    downloadItemWithRetryAndTimeout(url, retry - 1, e);
                }, 1000);
            }

        }

        downloadItemWithRetryAndTimeout(url, retry, null);
    });
}

This solution works, but it's an anti pattern as it breaks the promise chain: As each iteration returns a promise, just resolve the promise, and use .then to resolve the previous promise, and so on:

function downloadItemWithRetryAndTimeout(url, retry, failedReason) {
    return new Promise(function (resolve, reject) {
        try {
            if (retry < 0 && failedReason != null)
                reject(failedReason);

            downloadItem(url);
            resolve();
        } catch (e) {
            setTimeout(function () {
                downloadItemWithRetryAndTimeout(url, retry - 1, e).then(function () {
                    resolve();
                });
            }, 1000);
        }
    });
}

Solution 2

@BenjaminGruenbaum comment on @user663031 is fantastic but there's a slight error because this:

const delayError = (fn, ms) => fn().catch(e => delay(ms).then(y => Promise.reject(e)))

should actually be:

const delayError = (fn, ms) => () => fn().catch(e => delay(ms).then(y => Promise.reject(e)))

so it will return a function, not a promise. It's a tricky error that's hard to solve so I'm posting on here in case anyone needs it. Here's the whole thing:

const retry = (fn, retries = 3) => fn().catch(e => retries <= 0 ? Promise.reject(e) : retry(fn, retries - 1))
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const delayError = (fn, ms) => () => fn().catch(e => delay(ms).then(y => Promise.reject(e)))
retry(delayError(download, 1000))
Share:
12,097
AlexD
Author by

AlexD

Programming is just a fancy term for playing with words.

Updated on June 15, 2022

Comments

  • AlexD
    AlexD almost 2 years

    I have a function downloadItem that may fail for network reasons, I want to be able to retry it a few times before actually rejecting that item. The retries need to be with a timeout since if there is a network issue there is no point in retrying immediately.

    Here is what I have so far:

    function downloadItemWithRetryAndTimeout(url, retry, failedReason) {
        return new Promise(function(resolve, reject) {
            try {
                if (retry < 0 && failedReason != null) reject(failedReason);
    
                downloadItem(url);
                resolve();
            } catch (e) {
                setTimeout(function() {
                    downloadItemWithRetryAndTimeout(url, retry - 1, e);
                }, 1000);
            }
        });
    }
    

    Obviously this will fail since the second (and on) time I call downloadItemWithRetryAndTimeout I don't return a promise as required.

    How do I make it work correctly with that second promise?

    P.S. incase it matters the code is running in NodeJS.