Рекурсивное обещание в javascript
Я пишу Javascript Promise
, который находит окончательный URL-адрес переадресации ссылки.
Я делаю запрос HEAD
в Promise
с помощью XMLHttpRequest
. Затем, при загрузке, проверьте статус HTTP для чего-то в диапазоне 300 или если у него есть responseURL
, прикрепленный к объекту, и этот 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;
}
Затем, чтобы использовать эту функцию, я делаю что-то вроде:
getRedirectUrl(myUrl).then(function (url) { ... });
Проблема в том, что resolve();
в getRedirectUrl
вызовет then()
из вызывающей функции до того, как он вызовет рекурсивный вызов getRedirectUrl
, и в этот момент URL-адрес undefined
.
Я пытался, а не p.then(...getRedirectUrl...)
делать return self.getRedirectUrl(...)
, но это никогда не будет разрешено.
Моя догадка заключается в том, что шаблон, который я использую (который я в основном придумал на лету), не прав, вообще.
Ответы
Ответ 1
Проблема в том, что обещание, которое вы возвращаете из 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;
});
}
Ответ 2
Вот упрощенное решение:
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'));
Ответ 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);
Ответ 4
Если вы находитесь в среде, которая поддерживает 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
, функция/обещание разрешается до разрешения исходного вызова, точно так же, как и при разрешении Promise
в шаблоне Promise
.
Таким образом, вы можете вызывать getRedirectUrl(someUrl).then(...).catch(...)
точно так же, как в исходном вопросе.
Вероятно, следует отметить, что использование XHR для разрешения перенаправленного URL не удастся для любого URL, который не содержит правильный заголовок CORS.
В качестве дополнительного бонуса, async/await делает итеративный подход тривиальным.
async function getRedirectUrl(url, maxRetries = 10) {
for (let i = 0; i < maxRetries; i++) {
const xhr = await createXHRPromise(url);
if (xhr.status >= 300 && xhr.status < 400) {
url = xhr.getResponseHeader("Location");
} else if (xhr.responseURL && xhr.responseURL !== url) {
url = xhr.responseURL;
} else {
return url;
}
}
throw new Error('Redirected too many times');
}
Ответ 5
Следующее имеет две функции:
- _getRedirectUrl - имитация объекта setTimeout для поиска одношагового поиска перенаправленного URL-адреса (это эквивалентно одному экземпляру вашего запроса HEAD XMLHttpRequest)
- getRedirectUrl - это рекурсивные вызовы Обещает найти URL перенаправления
Секретным соусом является подчиненное обещание, успешное завершение которого вызовет функцию 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>