我正在编写一个Javascript Promise
,用于查找链接的最终重定向网址。
我正在做的是使用HEAD
在Promise
中发出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(...)
,但这永远不会解决。
我的猜测是,我正在使用的模式(我基本上是想出来的)是不对的。
答案 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)
以下具有两个功能:
秘密调味料是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 function
是Promise
的语法糖await
是Promise.then()
的语法糖return
中的async function
是resolve()
的语法糖throw
中的async function
是reject()
的语法糖如果async function
返回另一个async function
调用或Promise
,则该函数/承诺将在原始调用解析之前解析,与解析{{1}的方式完全相同}将采用Promise
模式。
因此,您可以完全像原始问题一样拨打Promise
。
应该注意的是,对于任何不包含正确的CORS标头的URL,使用XHR解析重定向的URL都会失败。
作为额外的好处,异步/等待使迭代方法变得微不足道。
getRedirectUrl(someUrl).then(...).catch(...)
答案 5 :(得分:0)
请检查以下示例以了解javascript / typescript中的递归承诺,直到数量增加到大于13时,它才能解析承诺。
下面的代码适用于打字稿,对javascript稍作修改。
ByVal wReadSize