リンクの最終リダイレクトURLを見つけるJavascript Promise
を書いています。
私がやっていることは、HEAD
を使用してPromise
でXMLHttpRequest
リクエストを行うことです。次に、ロード時に、HTTPステータスで300の範囲内の何かを確認します。または、オブジェクトにresponseURL
が添付されており、そのURLが片手で渡されたものと異なる場合。
どちらも当てはまらない場合は、resolve(url)
です。それ以外の場合、応答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) { ... });
_
問題は、getRedirectUrl
内のresolve();
がgetRedirectUrl
再帰呼び出しを呼び出す前に呼び出し元の関数からthen()
を呼び出し、その時点で、 URLはundefined
です。
p.then(...getRedirectUrl...)
をreturn self.getRedirectUrl(...)
するのではなく試みましたが、これは決して解決しません。
私が推測しているのは、私が使用しているパターン(基本的にその場で思いついたもの)が正しくないということです。
問題は、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();
});
_
thatで.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;
});
}
_
簡単なソリューションを次に示します。
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'));
多くのプログラミング言語で行ったように、与えられた数のfactorial
を返す以下の例を確認してください。
JavaScript
promiseを使用して以下の例を実装しました。
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);
次の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()
関数は、解決策の1ステップを迷路に進めるために再帰的に呼び出されます。 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>
_
async
/await
をサポートする環境にいる場合(事実上すべての現代の環境がサポートしています)、再帰関数パターンに少し似ている_async function
_を書くことができますそして愛。 Promise
の性質上、XMLHttpRequest
イベントを介してのみ値を取得するため(load
自体を公開するのではなく)、Promise
を完全に回避することはできませんが、再帰的呼び出しを行う関数の性質はおなじみのはずです。
この質問を最初に書いたときよりも4年以上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を解決すると、適切なCORSヘッダーが含まれていないURLに対して失敗することに注意してください。
追加のボーナスとして、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');
}
_