dfoverdx dfoverdx - 5 months ago 23
Javascript Question

Recursive Promise in javascript

I'm writing a Javascript

Promise
that finds the final redirect URL of a link.

What I'm doing is making a
HEAD
request in a
Promise
using an
XMLHttpRequest
. Then, on load, check the HTTP Status for something in the 300 range, or if it has a
responseURL
attached to the object and that url is different than the it was one handed.

If neither of these are true, I
resolve(url)
. Otherwise, I recursively call
getRedirectUrl()
on the response URL, and
resolve()
.

Here's my code:

function getRedirectUrl(url, maxRedirects) {
maxRedirects = maxRedirects || 0;
if (maxRedirects > 10) {
throw new Error("Redirected too many times.");
}

var xhr = new XMLHttpRequest();
var p = new Promise(function (resolve) {
xhr.onload = function () {
var redirectsTo;
if (this.status < 400 && this.status >= 300) {
redirectsTo = this.getResponseHeader("Location");
} else if (this.responseURL && this.responseURL != url) {
redirectsTo = this.responseURL;
}

if (redirectsTo) {
// check that redirect address doesn't redirect again
// **problem line**
p.then(function () { self.getRedirectUrl(redirectsTo, maxRedirects + 1); });
resolve();
} else {
resolve(url);
}
}

xhr.open('HEAD', url, true);
xhr.send();
});

return p;
}


Then to use this function I do something like:

getRedirectUrl(myUrl).then(function (url) { ... });


The issue is that
resolve();
in
getRedirectUrl
will call the
then()
from the calling function before it calls the
getRedirectUrl
recursive call, and at that point, the URL is
undefined
.

I tried, rather than
p.then(...getRedirectUrl...)
doing
return self.getRedirectUrl(...)
but this will never resolve.

My guess is that the pattern I'm using (that I basically came up with on the fly) isn't right, altogether.

Answer

The problem is that the promise you return from getRedirectUrl() needs to include the entire chain of logic to get to the URL. You're just returning a promise for the very first request. The .then() you're using in the midst of your function isn't doing anything.

To fix this:

Create a promise that resolves to redirectUrl for a redirect, or nothing otherwise:

var p = new Promise(function (resolve) {
    var xhr = new XMLHttpRequest();

    xhr.onload = function () {
        var redirectsTo;

        if (xhr.status < 400 && xhr.status >= 300) {
            redirectsTo = xhr.getResponseHeader("Location");
        } else if (xhr.responseURL && xhr.responseURL != url) {
            redirectsTo = xhr.responseURL;
        }

        resolve(redirectsTo);
    };

    xhr.open('HEAD', url, true);
    xhr.send();
});

Use .then() on that to return the recursive call, or not, as needed:

return p.then(function (redirectsTo) {
    if (redirectsTo) {
        return getRedirectUrl(redirectsTo, redirectCount+ 1);
    }
    return url;
});

Full solution:

function getRedirectUrl(url, redirectCount) {
    redirectCount = redirectCount || 0;
    if (redirectCount > 10) {
        throw new Error("Redirected too many times.");
    }

    return new Promise(function (resolve) {
        var xhr = new XMLHttpRequest();

        xhr.onload = function () {
            var redirectsTo;

            if (xhr.status < 400 && xhr.status >= 300) {
                redirectsTo = xhr.getResponseHeader("Location");
            } else if (xhr.responseURL && xhr.responseURL != url) {
                redirectsTo = xhr.responseURL;
            }

            resolve(redirectsTo);
        };

        xhr.open('HEAD', url, true);
        xhr.send();
    })
    .then(function (redirectsTo) {
        if (redirectsTo) {
            return getRedirectUrl(redirectsTo, redirectCount + 1);
        }
        return url;
    });
}
Comments