Recursieve belofte in javascript

Ik schrijf een Javascript Promisedie de uiteindelijke omleidings-URL van een link vindt.

Wat ik doe is een HEAD-verzoek indienen in een Promisemet behulp van een XMLHttpRequest. Controleer vervolgens bij het laden de HTTP-status op iets in het bereik van 300, of als er een responseURLaan het object is gekoppeld en die url anders is dan die met één hand.

Als geen van beide waar is, resolve(url). Anders roep ik recursief getRedirectUrl()aan op de antwoord-URL, en resolve().

Hier is mijn 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;
}

Om deze functie vervolgens te gebruiken, doe ik zoiets als:

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

Het probleem is dat resolve();in getRedirectUrlde then()van de aanroepende functie zal aanroepen voordat het de getRedirectUrlrecursieve aanroep, en op dat moment is de URL undefined.

Ik heb geprobeerd in plaats van p.then(...getRedirectUrl...)return self.getRedirectUrl(...)te doen, maar dit zal nooit oplossen.

Mijn gok is dat het patroon dat ik gebruik (dat ik eigenlijk ter plekke heb bedacht) helemaal niet klopt.


Antwoord 1, autoriteit 100%

Het probleem is dat de belofte die je terugstuurt van getRedirectUrl()de hele logica moet bevatten om bij de URL te komen. Je beantwoordt gewoon een belofte voor het allereerste verzoek. De .then()die u midden in uw functie gebruikt, doet niets.

Om dit op te lossen:

Maak een belofte die wordt omgezet in redirectUrlvoor een omleiding, of anders null:

function getRedirectsTo(xhr) {
    if (xhr.status < 400 && xhr.status >= 300) {
        return xhr.getResponseHeader("Location");
    }
    if (xhr.responseURL && xhr.responseURL != url) {
        return xhr.responseURL;
    }
    return null;
}
var p = new Promise(function (resolve) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () {
        resolve(getRedirectsTo(xhr));
    };
    xhr.open('HEAD', url, true);
    xhr.send();
});

Gebruik .then()op datom de recursieve aanroep terug te sturen, of niet, indien nodig:

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

Volledige oplossing:

function getRedirectsTo(xhr) {
    if (xhr.status < 400 && xhr.status >= 300) {
        return xhr.getResponseHeader("Location");
    }
    if (xhr.responseURL && xhr.responseURL != url) {
        return xhr.responseURL;
    }
    return null;
}
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 () {
            resolve(getRedirectsTo(xhr));
        };
        xhr.open('HEAD', url, true);
        xhr.send();
    })
    .then(function (redirectsTo) {
        return redirectsTo
            ? getRedirectUrl(redirectsTo, redirectCount + 1)
            : url;
    });
}

Antwoord 2, autoriteit 58%

Hier is de vereenvoudigde oplossing:

const recursiveCall = (index) => {
    return new Promise((resolve) => {
        console.log(index);
        if (index < 3) {
            return resolve(recursiveCall(++index))
        } else {
            return resolve()
        }
    })
}
recursiveCall(0).then(() => console.log('done'));

Antwoord 3, autoriteit 14%

Het volgende heeft twee functies:

  • _getRedirectUrl – wat een setTimeout-objectsimulatie is voor het opzoeken van een zoekopdracht in één stap van een omgeleide URL (dit komt overeen met een enkele instantie van uw XMLHttpRequest HEAD-verzoek)
  • getRedirectUrl – wat recursieve oproepen Beloften om de omleidings-URL op te zoeken

De geheime saus is de sub-belofte waarvan de succesvolle voltooiing een oproep tot resolve() van de bovenliggende belofte zal activeren.

function _getRedirectUrl( url ) {
    return new Promise( function (resolve) {
        const redirectUrl = {
            "https://mary"   : "https://had",
            "https://had"    : "https://a",
            "https://a"      : "https://little",
            "https://little" : "https://lamb",
        }[ url ];
        setTimeout( resolve, 500, redirectUrl || url );
    } );
}
function getRedirectUrl( url ) {
    return new Promise( function (resolve) {
        console.log("* url: ", url );
        _getRedirectUrl( url ).then( function (redirectUrl) {
            // console.log( "* redirectUrl: ", redirectUrl );
            if ( url === redirectUrl ) {
                resolve( url );
                return;
            }
            getRedirectUrl( redirectUrl ).then( resolve );
        } );
    } );
}
function run() {
    let inputUrl = $( "#inputUrl" ).val();
    console.log( "inputUrl: ", inputUrl );
    $( "#inputUrl" ).prop( "disabled", true );
    $( "#runButton" ).prop( "disabled", true );
    $( "#outputLabel" ).text( "" );
    getRedirectUrl( inputUrl )
    .then( function ( data ) {
        console.log( "output: ", data);
        $( "#inputUrl" ).prop( "disabled", false );
        $( "#runButton" ).prop( "disabled", false );
        $( "#outputLabel").text( data );
    } );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Input:
<select id="inputUrl">
    <option value="https://mary">https://mary</option>
    <option value="https://had">https://had</option>
    <option value="https://a">https://a</option>
    <option value="https://little">https://little</option>
    <option value="https://lamb">https://lamb</option>
</select>
Output:
<label id="outputLabel"></label>
<button id="runButton" onclick="run()">Run</button>

Other episodes