javascript中的递归Promise

时间:2015-03-12 21:39:40

标签: javascript recursion promise

我正在编写一个Javascript Promise,用于查找链接的最终重定向网址。

我正在做的是使用HEADPromise中发出XMLHttpRequest个请求。然后,在加载时,检查300状态中的某些内容的HTTP状态,或者是否有一个responseURL附加到该对象,并且该url与它是单手的不同。

如果这些都不属实,我resolve(url)。否则,我递归地在响应网址上调用getRedirectUrl(),并resolve()

这是我的代码:

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

然后使用此功能我会做:

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

问题是resolve();中的getRedirectUrl会在调用then()递归调用之前从调用函数调用getRedirectUrl,此时,URL是undefined

我尝试过,而不是p.then(...getRedirectUrl...)return self.getRedirectUrl(...),但这永远不会解决。

我的猜测是,我正在使用的模式(我基本上是想出来的)是不对的。

6 个答案:

答案 0 :(得分:35)

问题是你从getRedirectUrl()返回的承诺需要包含整个逻辑链来获取URL。您只是回复了第一个请求的承诺。您在功能中使用的.then()并未执行任何操作。

解决此问题:

创建一个针对重定向解析为redirectUrl的承诺,否则为其他任何内容:

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

上使用.then() 根据需要返回递归调用:

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

完整解决方案:

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) {
        return redirectsTo
            ? getRedirectUrl(redirectsTo, redirectCount+ 1)
            : url;
    });
}

答案 1 :(得分:4)

这是简化的解决方案:

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

答案 2 :(得分:3)

请检查以下示例,该示例将返回给定数字的 factorial ,就像在许多编程语言中一样。

我已经使用 JavaScript 承诺实现了以下示例。

let code = (function(){
	let getFactorial = n =>{
		return new Promise((resolve,reject)=>{
			if(n<=1){
				resolve(1);
			}
			resolve(
				getFactorial(n-1).then(fact => {
					return fact * n;
				})
			)
		});
	}
	return {
		factorial: function(number){
			getFactorial(number).then(
				response => console.log(response)
			)
		}
	}
})();
code.factorial(5);
code.factorial(6);
code.factorial(7);

答案 3 :(得分:2)

以下具有两个功能:

  • _getRedirectUrl-这是一个setTimeout对象模拟,用于查找重定向URL的单步查找(这等效于XMLHttpRequest HEAD请求的单个实例)
  • getRedirectUrl-这是递归调用Promise来查找重定向URL

秘密调味料是Promise子程序,它的成功完成将触发来自父承诺的对resolve()的调用。

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>

作为递归承诺的另一个例证,我用它来解决一个迷宫。 Solve()函数被递归调用,以在迷宫解决方案中前进一个步骤,否则在遇到死角时回退。 setTimeout函数用于将解决方案的动画设置为每帧100ms(即10hz帧速率)。

const MazeWidth = 9
const MazeHeight = 9

let Maze = [
    "# #######",
    "#   #   #",
    "# ### # #",
    "# #   # #",
    "# # # ###",
    "#   # # #",
    "# ### # #",
    "#   #   #",
    "####### #"
].map(line => line.split(''));

const Wall = '#'
const Free = ' '
const SomeDude = '*'

const StartingPoint = [1, 0]
const EndingPoint = [7, 8]

function PrintDaMaze()
{
    //Maze.forEach(line => console.log(line.join('')))
    let txt = Maze.reduce((p, c) => p += c.join('') + '\n', '')
    let html = txt.replace(/[*]/g, c => '<font color=red>*</font>')
    $('#mazeOutput').html(html)
}

function Solve(X, Y) {

    return new Promise( function (resolve) {
    
        if ( X < 0 || X >= MazeWidth || Y < 0 || Y >= MazeHeight ) {
            resolve( false );
            return;
        }
        
        if ( Maze[Y][X] !== Free ) {
            resolve( false );
            return;
        }

        setTimeout( function () {
        
            // Make the move (if it's wrong, we will backtrack later)
            Maze[Y][X] = SomeDude;
            PrintDaMaze()

            // Check if we have reached our goal.
            if (X == EndingPoint[0] && Y == EndingPoint[1]) {
                resolve(true);
                return;
            }

            // Recursively search for our goal.
            Solve(X - 1, Y)
            .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X + 1, Y);
            } )
            .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X, Y - 1);
             } )
             .then( function (solved) {
                if (solved) return Promise.resolve(solved);
                return Solve(X, Y + 1);
             } )
             .then( function (solved) {
                 if (solved) {
                     resolve(true);
                     return;
                 }

                 // Backtrack
                 setTimeout( function () {
                     Maze[Y][X] = Free;
                     PrintDaMaze()
                     resolve(false);
                 }, 100);
                 
             } );

        }, 100 );
    } );
}

Solve(StartingPoint[0], StartingPoint[1])
.then( function (solved) {
    if (solved) {
        console.log("Solved!")
        PrintDaMaze()
    }
    else
    {
        console.log("Cannot solve. :-(")
    }
} );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<pre id="mazeOutput">
</pre>

答案 4 :(得分:0)

如果您在支持async / await的环境中(几乎所有现代环境都支持),则可以编写看起来更像递归功能模式的async function我们都知道和爱。由于Promise的性质仅通过XMLHttpRequest事件(而不是公开load本身)来获取值,所以无法完全避免使用Promise,而是递归的使调用看起来很熟悉的函数的性质。

比起初写此问题时,我拥有超过四年的JavaScript经验,因此我对代码进行了一些整理,但其工作方式基本上相同。

// creates a simple Promise that resolves the xhr once it has finished loading
function createXHRPromise(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();

        // addEventListener('load', ...) is basically the same as setting
        // xhr.onload, but is better practice
        xhr.addEventListener('load', () => resolve(xhr));

        // throw in some error handling so that the calling function 
        // won't hang
        xhr.addEventListener('error', reject);
        xhr.addEventListener('abort', reject);

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

async function getRedirectUrl(url, maxRetries = 10) {
    if (maxRetries <= 0) {
        throw new Error('Redirected too many times');
    }

    const xhr = await createXHRPromise(url);
    if (xhr.status >= 300 && xhr.status < 400) {
        return getRedirectUrl(xhr.getResponseHeader("Location"), maxRetries - 1);
    } else if (xhr.responseURL && xhr.responseURL !== url) {
        return getRedirectUrl(xhr.responseURL, maxRetries - 1);
    }

    return url;
}

async / await的简短解释

  • async functionPromise的语法糖
  • awaitPromise.then()的语法糖
  • return中的
  • async functionresolve()的语法糖
  • throw中的
  • async functionreject()的语法糖

如果async function返回另一个async function调用或Promise,则该函数/承诺将在原始调用解析之前解析,与解析{{1}的方式完全相同}将采用Promise模式。

因此,您可以完全像原始问题一样拨打Promise

应该注意的是,对于任何不包含正确的CORS标头的URL,使用XHR解析重定向的URL都会失败。


作为额外的好处,异步/等待使迭代方法变得微不足道。

getRedirectUrl(someUrl).then(...).catch(...)

答案 5 :(得分:0)

请检查以下示例以了解javascript / typescript中的递归承诺,直到数量增加到大于13时,它才能解析承诺。

下面的代码适用于打字稿,对javascript稍作修改。

ByVal wReadSize