Recursive Promise in javascript Recursive Promise in javascript javascript javascript

Recursive Promise in javascript


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 null otherwise:

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();});

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

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

Full solution:

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;    });}


Here's the simplified solution:

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'));


The following has two functions:

  • _getRedirectUrl - which is a setTimeout object simulation for looking up a single step lookup of a redirected URL (this is equivalent to a single instance of your XMLHttpRequest HEAD request)
  • getRedirectUrl - which is recursive calls Promises to lookup the redirect URL

The secret sauce is the sub Promise whose's successful completion will trigger a call to resolve() from the parent promise.

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>