Ajaxのリクエストを行う関数foo
があります。どうすればfoo
からのレスポンスを返すことができますか?
成功`のコールバックから値を返したり、関数内のローカル変数にレスポンスを代入してそれを返したりしてみましたが、どれも実際にはレスポンスを返してくれませんでした。
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- I tried that one as well
}
});
return result;
}
var result = foo(); // It always ends up being `undefined`.
→ 非同期動作のより一般的な説明とさまざまな例については、 https://stackoverflow.com/q/23667086/218196 を参照してください。
*→ すでに問題を理解している場合は、以下の可能な解決策に進んでください。
問題点
Ajax]1のAは、asynchronousの略です。つまり、リクエストの送信(というかレスポンスの受信)が、通常の実行フローから外れることを意味します。あなたの例では、
$.ajax
はすぐに戻り、次の文であるreturn result;
は、success
コールバックとして渡された関数が呼び出される前に実行されています。 ここで、同期フローと非同期フローの違いをより明確にするために、例え話をしてみましょう。同期
友人に電話をかけて、何かを調べてくれるように頼んだとします。時間がかかるかもしれませんが、友人が必要な答えを教えてくれるまで、あなたは電話の前で空間を見つめて待っています。 これと同じことが、「通常の」コードを含む関数呼び出しを行ったときにも起こります。
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
findItemの実行には時間がかかるかもしれませんが、
var item = findItem();` の後に来るコードは、関数が結果を返すまで *待たなければなりません。
同じ理由で再び友人に電話をかけます。しかし今度は、急いでいるので携帯電話で折り返し電話してほしいと伝えます。あなたは電話を切り、家を出て、予定していたことをすべて実行します。友人があなたに電話をかけてきたら、あなたはその友人があなたに伝えた情報を扱うことになります。 これはまさに、Ajaxリクエストを行うときに起こっていることです。
findItem(function(item) {
// Do something with item
});
doSomethingElse();
JavaScriptの非同期性を受け入れましょう!ある種の非同期操作は同期的な対応策を提供していますが("Ajax "もそうです)、特にブラウザのコンテキストでは、一般的にそれらを使用することは推奨されません。 なぜそれがいけないのか? JavaScriptはブラウザのUIスレッドで実行されるため、長時間の処理を行うとUIがロックされ、反応しなくなります。さらに、JavaScriptの実行時間には上限があり、ブラウザは実行を続けるかどうかをユーザーに尋ねます。 これらはすべて、本当に悪いユーザーエクスペリエンスです。ユーザーは、すべてが正常に動作しているのかどうかを判断できません。さらに、接続速度が遅いユーザーの場合、効果はさらに大きくなります。 以下では、3つの異なるソリューションを紹介します。
2017年にリリースされたECMAScriptのバージョンでは、非同期関数の構文レベルのサポートが導入されました。asyncと
awaitの助けを借りて、非同期を「同期スタイル」で書くことができます。コードは非同期のままですが、読みやすく、理解しやすくなります。 async/await
は約束事の上に構築されています。async
関数は常に約束事を返します。easync関数は常にpromiseを返します。
awaitはpromiseを "unwrap "して、promiseが解決された値を結果として返すか、promiseが拒否された場合はエラーを投げます。 **重要:**
awaitは
async関数の内部でのみ使用できます。今のところ、トップレベルの
awaitはまだサポートされていないので、非同期 IIFE ([Immediately Invoked Function Expression](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression)) を作って
asyncコンテキストを開始する必要があるかもしれません。 async
]3やawait
についてはMDNで詳しく説明されています。
以下は、上記のディレイをベースにした例です。
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
async/await
をサポートしています。また、regenerator(またはBabelのようなregeneratorを使用するツール)の助けを借りて、コードをES5に変換することで、古い環境をサポートすることができます。コールバックとは、単に他の関数に渡される関数のことです。他の関数は、準備ができたときにいつでも渡された関数を呼び出すことができます。非同期処理の文脈では、非同期処理が終了するたびにコールバックが呼び出されます。通常、コールバックには結果が渡されます。
質問の例では、foo
にコールバックを受け取らせて、それをsuccess
コールバックとして使うことができます。つまりこれは
var result = foo();
// Code that depends on 'result'
は次のようになります。
foo(function(result) {
// Code that depends on 'result'
});
ここでは関数を「inline」と定義しましたが、任意の関数参照を渡すことができます。
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
自体は次のように定義されています。
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
は foo
を呼び出したときに渡した関数を参照し、それを success
に渡すだけです。つまり、Ajax リクエストが成功すると、$.ajax
は callback
を呼び出し、レスポンスをコールバックに渡します (コールバックを定義した方法なので、result
を使って参照できます)。
レスポンスを処理してからコールバックに渡すこともできます。
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Promise API]9はECMAScript 6 (ES2015)の新機能ですが、すでに十分なブラウザサポートが行われています。また、標準のPromise APIを実装し、非同期関数の使用や構成を容易にするための追加メソッドを提供するライブラリも数多く存在します(例:bluebird)。 プロミスは未来の値のコンテナです。プロミスは、値を受け取ったとき(※解決されたとき)、またはキャンセルされたとき(※拒否されたとき)、その値にアクセスしたい「リスナー」のすべてに通知します。 普通のコールバックと比べて、コードを切り離すことができ、構成が簡単になるという利点があります。 以下は、プロミスを使った簡単な例です。
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
Ajaxの呼び出しに適用すると、次のようにプロミスを使うことができます。
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
プロミスが提供するすべての利点を説明することは、この回答の範囲を超えていますが、新しいコードを書く場合には、プロミスを真剣に検討すべきです。プロミスは、コードの優れた抽象化と分離を実現します。 プロミスについての詳細はこちら。HTML5 rocks - JavaScript Promisesを参照してください。
遅延オブジェクトは、(Promise APIが標準化される前の)jQuery独自のプロミスの実装です。プロミスとほぼ同様の動作をしますが、若干異なるAPIを公開しています。 jQueryのすべてのAjaxメソッドはすでに「遅延オブジェクト」(実際には遅延オブジェクトの約束)を返しており、それを関数から返すことができます。
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
プロミスや遅延オブジェクトは、将来の値の単なる「入れ物」であり、値そのものではないことに注意してください。例えば、次のようなものがあったとします。
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
このコードは、上記の非同期性の問題を誤解しています。具体的には、$.ajax()
は、サーバー上の'/password'ページをチェックしている間、コードをフリーズさせているのではなく、サーバーにリクエストを送り、待っている間に、サーバーからのレスポンスではなく、jQuery Ajax Deferredオブジェクトを即座に返しているのです。つまり、if
文は常にこのDeferredオブジェクトを取得し、それをtrue
として扱い、ユーザーがログインしているかのように処理することになります。これは良くありません。
しかし、修正は簡単です。
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
先に述べたように、いくつかの(!)非同期操作には同期対応のものがあります。その使用を推奨するわけではありませんが、念のため、以下に同期的な呼び出しを行う方法を示します。
XMLHTTPRequest][14]オブジェクトを直接使用する場合は、[
.open][15]の第3引数に
false`を渡します。
jQuery]16 を使用する場合は、async
オプションを false
に設定します。なお、このオプションは jQuery 1.8 以降では 非推奨 となっています。
この場合、引き続き success
コールバックを使用するか、jqXHR オブジェクトの responseText
プロパティにアクセスすることができます。
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
他の jQuery Ajax メソッド、例えば $.get
や $.getJSON
などを使用している場合は、$.ajax
に変更する必要があります (構成パラメータは $.ajax
にしか渡すことができないため)。
注意! 同期的なJSONPリクエストを行うことはできません。JSONPはその性質上、常に非同期です(このオプションを検討しない理由がもう一つあります)。
あなたのコードは、以下のようなものになるはずです。
function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // always ends up being 'undefined'
jQuery for AJAXを使っている人のためにFelix Kling氏が素晴らしい仕事をしてくれたので、そうでない人のために代替案を提供することにしました。
(Note, for those using the new fetch
API, Angular or promises I've added another answer below)
これは別の回答にある「問題の説明」を短くまとめたもので、これを読んでもよくわからない場合はそちらを読んでください。
AJAXのAはasynchronousの略です。つまり、リクエストの送信(というかレスポンスの受信)が、通常の実行フローから外されるということです。あなたの例では、.send
はすぐに戻り、次の文であるreturn result;
は、success
コールバックとして渡された関数が呼び出される前に実行されます。
つまり、リターンするときには、定義したリスナーがまだ実行されていないので、リターンする値が定義されていないことになります。
以下に簡単な例えを示します。
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
[(Fiddle)][2]を参照してください。
a=5の部分がまだ実行されていないので、返される
aの値は
undefined` です。AJAXはこのように、サーバーがブラウザにその値を伝える機会を得る前に、値を返してしまうのです。
この問題を解決する一つの方法は、計算が完了したときに何をすべきかをプログラムに伝える、_re-actively_のコードを書くことです。
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
これをCPSと呼びます。基本的には、getFive
に計算が完了したときに実行するアクションを渡し、イベントが完了したときにどのように反応するかをコードに伝えています (AJAX 呼び出しや、この場合はタイムアウトなど)。
使い方は次のようになります。
getFive(onComplete);
これで画面に「5」と表示されるはずです。[(Fiddle)][4]です。
この問題を解決するには、基本的に2つの方法があります。
1.AJAXコールを同期させる(SJAXと呼ぶ)。 2.コールバックで正しく動作するようにコードを再構築する。
同期型AJAXについては、やめておきましょう! Felixの回答では、なぜそれが悪いアイデアなのか、説得力のある議論が展開されています。要約すると、サーバーがレスポンスを返すまでユーザーのブラウザをフリーズさせ、非常に悪いユーザーエクスペリエンスを生み出すことになります。その理由について、MDNから抜粋した別の短いまとめを紹介します。
XMLHttpRequestは、同期通信と非同期通信の両方をサポートしています。XMLHttpRequestは、同期通信と非同期通信の両方をサポートしていますが、一般的には、パフォーマンス上の理由から、同期通信よりも非同期通信の方が望ましいとされています。
XMLHttpRequestは、同期通信と非同期通信の両方をサポートしています。
どうしてもそうしたい場合は、フラグを渡します。以下はその方法です:.
var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {// That's HTTP for 'ok'
console.log(request.responseText);
}
関数にコールバックを受け取らせる。例題のコードでは foo
にコールバックを受け取らせることができます。ここでは、foo
が完了したときにどのように react するかをコードに記述します。
つまり
var result = foo();
// code that depends on `result` goes here
となります。
foo(function(result) {
// code that depends on `result`
});
ここでは無名関数を渡していますが、既存の関数への参照を渡すことも簡単にできますので、次のようになります。
function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
このようなコールバックの設計方法の詳細については、Felixの回答を参照してください。
では、それに応じて動作するように foo 自身を定義してみましょう。
function foo(callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onload = function(){ // when the request is loaded
callback(httpRequest.responseText);// we're calling our method
};
httpRequest.open('GET', "/echo/json");
httpRequest.send();
}
[(fiddle)][6]を参照してください。
AJAXが正常に完了したときに実行されるアクションをfoo関数が受け入れるようにしました。これをさらに拡張して、応答ステータスが200でないかどうかをチェックし、それに応じて行動することができます(failハンドラなどを作成します)。効果的に問題を解決することができます。
まだ理解に苦しむ場合は、MDNのAJAX getting started guide]7を読んでください。
XMLHttpRequest]1 2 (まず、Benjamin GruenbaumとFelix Klingの回答を読んでください) もしあなたがjQueryを使わず、モダンブラウザやモバイルブラウザで動作する短いXMLHttpRequest 2が欲しいのであれば、このように使うことをお勧めします。
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
ご覧の通りです。 1.リストアップされた他の関数よりも短いです。 2.コールバックが直接設定されている(だから余分な不要なクロージャがない)。 3.新しいonloadを使用しているので、readystate & & statusをチェックする必要がありません。 4.他にも覚えていないが、XMLHttpRequest 1を悩ませている状況がある。 このAjaxコールのレスポンスを取得するには2つの方法があります(XMLHttpRequestのvar名を使った3つの方法)。 一番簡単なのは
this.response
あるいは、何らかの理由でコールバックをクラスに bind()
した場合。
e.target.response
例
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
あるいは(上の方がいいですね 匿名関数はいつも問題になります)。
ajax('URL', function(e){console.log(this.response)});
もし、postやFormDataを使ってもっと複雑なことをしたいなら、この関数を簡単に拡張することができます。
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.send(d||null)
}
繰り返しになりますが......これは非常に短い関数ですが、postを取得することができます。 使い方の例。
x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data
あるいは、完全なフォーム要素(document.getElementsByTagName('form')[0]
)を渡します。
var fd = new FormData(form);
x(url, callback, 'post', fd);
または、いくつかのカスタム値を設定します。
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
コメントにあるように、error && synchronousの使用は、回答のポイントを完全に壊します。適切な方法でAjaxを使用するための素敵な短い方法はどれですか? エラーハンドラ。
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.onerror = error;
c.send(d||null)
}
function error(e){
console.log('--Error--', this.type);
console.log('this: ', this);
console.log('Event: ', e)
}
function displayAjax(e){
console.log(e, this);
}
x('WRONGURL', displayAjax);
displayAjax()
の this.statusText
の下に Method not Allowed
として表示されます。
2つ目のケースでは、単純に動作します。正しいポストデータを渡したかどうか、サーバーサイドで確認する必要があります。
cross-domain not allowedは自動的にエラーを投げます。
エラーレスポンスには、エラーコードはありません。
エラーレスポンスにはエラーコードはなく、エラーに設定された this.type
があるだけです。
エラーを完全にコントロールできないのであれば、なぜエラーハンドラを追加するのでしょうか?
ほとんどのエラーは、コールバック関数 displayAjax()
の中で返されます。
だからURLを正しくコピー&ペーストできるなら、エラーチェックは必要ありません。 ;)
追記:最初のテストとして、x('x', displayAjax)...と書いたところ、全く反応がありませんでした...?そこで、HTMLが置かれているフォルダを確認したところ、「x.xml」というファイルがありました。つまり、ファイルの拡張子を忘れてしまっても、XMLHttpRequest 2はそれを見つけてくれるのです。笑いました*ファイルを同期して読む
**そんなことはしないでください。
もしあなたがしばらくの間ブラウザをブロックしたいのであれば、大きな .txt
ファイルを同期的に読み込んでください。
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
これで次のことができます。
var res = omg('thisIsGonnaBlockThePage.txt');
上記の関数は基本的な使用方法です。 もし、機能を拡張したい場合は...。 はい、できます。 私は多くのAPIを使用していますが、すべてのHTMLページに統合する最初の関数の1つは、この回答の最初のAjax関数で、GETのみです...。 でも、XMLHttpRequest 2を使えば、いろいろなことができます。 私はダウンロードマネージャー(レジューム、ファイルリーダー、ファイルシステムの両側の範囲を使用)、canvasを使用した様々な画像リサイザーコンバーター、base64画像を使用したWeb SQLデータベースの投入などを作成しました...しかし、このようなケースでは、その目的のためだけに関数を作るべきです...blobや配列バッファが必要な場合もありますし、ヘッダを設定したり、mimetypeをオーバーライドしたり、他にもたくさんあります...。 しかし、ここでの問題は、どのようにしてAjaxレスポンスを返すかということです...。(簡単な方法を追加しました。)