javascript中的递归Promise

dx_*_*_dt 35 javascript recursion promise

我正在写一个Javascript Promise,找到链接的最终重定向URL.

我正在做的是HEADPromise使用中发出请求XMLHttpRequest.然后,在加载时,检查HTTP状态以查找300范围内的某些内容,或者是否已responseURL附加到该对象,并且该URL与单手的URL不同.

如果这些都不是真的,我resolve(url).否则,我递归调用getRedirectUrl()响应URL,并且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;
}
Run Code Online (Sandbox Code Playgroud)

然后使用此功能我做的事情如下:

getRedirectUrl(myUrl).then(function (url) { ... });
Run Code Online (Sandbox Code Playgroud)

问题是resolve();in getRedirectUrlthen()在调用getRedirectUrl递归调用之前调用来自调用函数的调用,此时,URL就是undefined.

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

我的猜测是,我正在使用的模式(我基本上是在飞行中想出来的)并不完全正确.

JLR*_*she 44

问题是你返回的承诺getRedirectUrl()需要包括整个逻辑链来获取URL.你只是回复了第一个请求的承诺.在.then()您使用的是你的函数的中间没有做任何事情.

解决这个问题:

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

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();
});
Run Code Online (Sandbox Code Playgroud)

使用.then()返回递归调用,还是不行,需要:

return p.then(function (redirectsTo) {
    return redirectsTo
        ? getRedirectUrl(redirectsTo, redirectCount+ 1)
        : url;
});
Run Code Online (Sandbox Code Playgroud)

完整解决方案

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;
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 我糊涂了!我看不出这里怎么回事。如果我调用getRedirectUrl(),它将返回一个promise,该promise具有可完成某些工作的resolve()函数。完成这些工作后,可以递归以遵循重定向。但!第一个xhr.open()如何?如果xhr.send()发生的唯一位置在Promise resolve()函数中,是否曾经发生过?我对Promise和JavaScript还是陌生的,因此这里可能遗漏了一些东西。 (2认同)
  • @theWebalyst这里的细微之处在于,`resolve`函数实际上是传递给`Promise`构造函数的处理函数的参数。在您的思维中导致混乱的错误是* promise具有resolve()函数,可以完成某些工作。*。实际上,处理程序函数会做一些事情,并在完成时调用它传递的“ resolve”函数。您可以添加一个名为`reject`的第二个参数,并在重定向太多时调用它而不是`throw`。效果是一样的。 (2认同)

cud*_*ter 8

这是简化的解决方案:

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'));
Run Code Online (Sandbox Code Playgroud)

  • 如果索引足够大,您将遇到“超出最大调用堆栈大小”错误。 (2认同)
  • @balazs 好吧,你可以将这个 `returnsolve(recursiveCall(++index))` 更改为 `return setTimeout(() =&gt;resolve(recursiveCall(++index)), 0);` 就可以了。但这与问题没有直接关系,并且在简化的示例中可能会令人困惑 (2认同)

Ste*_*uan 7

下面有两个功能:

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

秘诀是子 Promise,它的成功完成将触发父 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 );
    } );

}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

作为递归承诺的另一个例子,我用它来解决一个迷宫。该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. :-(")
    }
} );
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<pre id="mazeOutput">
</pre>
Run Code Online (Sandbox Code Playgroud)


May*_*r S 5

请检查下面的示例,它将返回给factorial定的数字,就像我们在许多编程语言中所做的那样。

我已经使用JavaScriptPromise 实现了下面的示例。

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);
Run Code Online (Sandbox Code Playgroud)