私はAjaxリクエストを行う関数foo
を持っています。 foo
からの応答を返すにはどうすればいいですか?
私はsuccess
コールバックから値を返すことと、関数内のローカル変数に応答を代入してそれを返すことを試みました、しかし、これらの方法のどれも実際には応答を返しません。
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`.
→別の例での非同期動作のより一般的な説明については、を参照してください関数内で変数を変更した後、変数が変更されないのはなぜですか? - 非同期コード参照
→すでに問題を理解している場合は、以下の解決策に進んでください。
Ajax の _ a _ は、 非同期 を表します。つまり、リクエストを送信する(またはレスポンスを受信する)ことは、通常の実行フローから除外されます。あなたの例では、$.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();
の後に来るコードはすべて、関数が結果を返すまでwaitしなければなりません。
あなたは同じ理由であなたの友人にまた電話をかけます。しかし今度はあなたは急いでいるので彼にあなたの携帯電話であなたに電話をかけて _をすべきだと言います。あなたは電話を切り、家を出て、あなたが計画したことを何でもします。あなたの友人があなたに電話をかけたら、あなたは彼があなたに与えた情報を扱っています。
それがまさにあなたがAjaxリクエストをするときに起こっていることです。
findItem(function(item) {
// Do something with item
});
doSomethingElse();
応答を待つ代わりに、実行はすぐに続行され、Ajax呼び出し後のステートメントが実行されます。応答を最終的に取得するには、応答が受信されたときに呼び出される関数、コールバック(何かに注意してください?コールバック?)を指定します。その呼び出しの後に来るステートメントは、コールバックが呼び出される前に実行されます。
JavaScriptの非同期的な性質を取り入れてください。 特定の非同期操作では同期操作が提供されますが( "Ajax"でも同様です)、特にブラウザの状況では使用しないことをお勧めします。
なぜあなたはそれが悪いのですか?
JavaScriptはブラウザのUIスレッドで実行され、長時間実行されているプロセスはUIをロックして応答しなくなります。さらに、JavaScriptの実行時間には上限があり、ブラウザはユーザーに実行を続行するかどうかを尋ねます。
これらすべてが本当に悪いユーザーエクスペリエンスです。ユーザーは、すべてがうまく機能しているかどうかを見分けることができません。さらに、接続が遅いユーザにとっては、その効果はさらに悪くなります。
以下では、互いに重なり合って構築されている3つの異なるソリューションについて説明します。
async/await
で約束します(ES2017 +、トランスパイラーまたはリジェネレータを使用している場合は古いブラウザで使用可能)then()
で約束します (ES2015 +、たくさんの約束ライブラリのうちの1つを使うなら、より古いブラウザで利用可能です)3つすべてが現在のブラウザおよびノード7+で利用可能です。
async/await
で約束2017年にリリースされたECMAScriptバージョンでは、非同期関数用に構文レベルのサポートが導入されました。 async
とawait
の助けを借りて、あなたは "同期スタイル"で非同期を書くことができます。コードはまだ非同期ですが、読みやすく理解しやすいです。
async/await
はpromiseに基づいて構築されています。async
関数は常にpromiseを返します。 await
はプロミスを「アンラップ」し、プロミスが解決された値になるか、プロミスが拒否された場合はエラーをスローします。
重要: await
関数内でのみasync
を使用できます。現時点では、最上位のawait
はまだサポートされていないため、async
コンテキストを開始するには非同期IIFE( Immediately Invoked Function Expression )を作成する必要があります。
MDNで async
と await
の詳細を読むことができます。
これは上記の遅延の上に構築される例です:
// 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);
})();
現在の browser および node バージョンはasync/await
をサポートしています。 regenerator (または Babel のような蓄熱器を使用するツール)を使用してコードを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);
}
});
}
見かけよりもコールバックを使ってコードを書く方が簡単です。結局のところ、ブラウザ内のJavaScriptは非常にイベント駆動型(DOMイベント)です。 Ajaxレスポンスを受け取ることは、イベントに他なりません。
サードパーティのコードを使用しなければならない場合は問題が発生する可能性がありますが、ほとんどの問題はアプリケーションの流れを通して考えることで解決できます。
Promise API はECMAScript 6(ES2015)の新機能ですが、すでに ブラウザサポート を持っています。標準のPromises APIを実装し、非同期関数の使用と構成を容易にするための追加のメソッドを提供するライブラリも多数あります(例: bluebird )。
Promiseはfuture valuesの入れ物です。プロミスが値を受け取る(解決済み)、またはキャンセルされる(拒否)と、この値にアクセスするすべての "リスナー"に通知します。
プレーンコールバックよりも優れている点は、コードを分離することができ、作成が簡単だということです。
これが約束を使う簡単な例です:
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
});
約束のオファーの利点をすべて説明することはこの回答の範囲を超えていますが、新しいコードを書く場合は、それらを真剣に検討する必要があります。それらはあなたのコードの素晴らしい抽象化と分離を提供します。
Promiseに関するさらに詳しい情報: HTML5 rocks - JavaScript Promises
Deferred objects は、jQueryのpromiseのカスタム実装です(Promise APIが標準化される前)。それらはほぼ約束のように振舞いますが、わずかに異なるAPIを公開します。
JQueryのすべてのAjaxメソッドはすでに "deferredオブジェクト"(実際にはdeferredオブジェクトの約束)を返します。これは関数から返すことができます。
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
Promiseとdeferredオブジェクトは将来の価値のための単なるcontainerであり、それ自体が価値ではないことに留意してください。たとえば、次のようにしたとします。
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
オブジェクトを直接使用する場合は、3番目の引数としてfalse
を .open
に渡します。
jQuery を使用する場合は、async
オプションをfalse
に設定できます。 jQuery 1.8以降、このオプションは非推奨です。それでもsuccess
コールバックを使用することも、 jqXHRオブジェクト のresponseText
プロパティにアクセスすることもできます。
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
$.get
、$.getJSON
などの他のjQuery Ajaxメソッドを使用する場合は、それを$.ajax
に変更する必要があります($.ajax
には構成パラメーターしか渡すことができないため)。
ヘッズアップ! 同期の JSONP 要求を行うことはできません。その性質上、JSONPは常に非同期です(このオプションを考慮しないもう1つの理由)。
あなたのコードはこれに沿ったものでなければなりません:
function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // always ends up being 'undefined'
Felix Klingは、jQuery for AJAXを使用しているユーザー向けの回答を作成してきましたが、そうでないユーザー向けの代替手段を提供することにしました。
( 注、新しいfetch
API、Angular、または以下に別の回答を追加した約束を使用している場合 )
これは、他の回答からの「問題の説明」の短い要約です。これを読んだ後にわからない場合は、それを読んでください。
AJAXの-Aはasynchronousを表します。つまり、要求の送信(または応答の受信)は通常の実行フローから除外されます。この例では、 .send
はすぐに戻り、success
コールバックとして渡された関数が呼び出される前に、次のステートメントreturn result;
が実行されます。
これは、戻るときに、定義したリスナーがまだ実行されていないことを意味します。つまり、返す値は定義されていません。
これは簡単なアナロジーです
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
a=5
部分がまだ実行されていないため、返されるa
の値はundefined
です。 AJAXはこのように動作し、サーバーがブラウザにその値を知らせる機会を得る前に値を返します。
この問題の解決策の1つは、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) 。
これを解決するには、基本的に2つの方法があります。
同期AJAXに関しては、しないでください! Felixの答えは、なぜそれが悪い考えであるかについての説得力のある議論を引き起こします。まとめると、サーバーが応答を返すまでユーザーのブラウザーがフリーズし、ユーザーエクスペリエンスが非常に悪くなります。 MDNから得られた別の短い要約は次のとおりです。
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();
}
Foo関数がAJAXが正常に完了したときに実行するアクションを受け入れるようになりました。応答ステータスが200でないかどうかを確認し、それに応じて動作する(失敗ハンドラーを作成する)ことでこれをさらに拡張できます。問題を効果的に解決します。
まだこれを理解するのに苦労している場合 MDNでAJAX入門ガイドを読んでください 。
XMLHttpRequest 2 (最初に Benjamin Gruenbaumからの回答を読む & Felix Kling )
もしあなたがjQueryを使わず、最近のブラウザやモバイルブラウザでも動作するNice short XMLHttpRequest 2が欲しいなら、私はこの方法を使うことを勧めます:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
ご覧のように:
このAjax呼び出しの応答を取得する方法は2つあります(3つはXMLHttpRequest変数名を使用)。
最も簡単な:
this.response
あるいは何らかの理由であなたがクラスへのコールバックをbind()
した場合:
e.target.response
例:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
または(上記のものはより良い無名関数です常に問題です):
ajax('URL', function(e){console.log(this.response)});
簡単なことは何もありません。
今では一部の人々はおそらくonreadystatechangeまたはXMLHttpRequest変数名さえ使うほうが良いと言うでしょう。それは間違っている。
チェックアウト XMLHttpRequestの高度な機能
それはすべての*最近のブラウザをサポートしました。 XMLHttpRequest 2が存在するため、このアプローチを使用していることを確認できます。私が使っているすべてのブラウザで問題が起きたことは一度もありません。
onreadystatechangeは、状態2のヘッダーを取得したい場合にのみ役立ちます。
XMLHttpRequest
変数名を使用することは、onload/oreadystatechangeクロージャの中でコールバックを実行する必要があるので、もう一つの大きなエラーです。
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)
}
これも非常に短い関数ですが、取得して投稿することはできません。
使用例
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 && synchronizingの使用は完全に答えのポイントを壊しません。 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);
上記のスクリプトでは、静的に定義されたエラーハンドラがあるため、機能が損なわれることはありません。エラーハンドラは他の機能にも使用できます。
しかし、実際にエラーを発生させるには、 only の方法で間違ったURLを書くことになります。その場合、すべてのブラウザがエラーをスローします。
エラーハンドラは、カスタムヘッダを設定したり、responseTypeをBLOB配列バッファに設定したりする場合に便利です。
メソッドとして 'POSTAPAPAP'を渡してもエラーになりません。
Formdataとして 'fdggdgilfdghfldj'を渡してもエラーにはなりません。
最初のケースでは、エラーはthis.statusText
の下のdisplayAjax()
内にMethod not Allowed
としてあります。
後者の場合、それは単に機能します。正しい投稿データを渡したかどうかは、サーバー側で確認する必要があります。
クロスドメインが許可されていないと自動的にエラーが発生します。
エラーレスポンスにはエラーコードはありません。
Errorに設定されているのはthis.type
だけです。
エラーを完全に制御できない場合は、なぜエラーハンドラを追加するのですか?エラーのほとんどは、コールバック関数displayAjax()
の中でこの内部に返されます。
そのため、URLを正しくコピーして貼り付けることができれば、エラーチェックは不要です。 ;)
PS:最初のテストとしてx( 'x'、displayAjax)を書きましたが、完全に応答がありました... ???そこで、HTMLがあるフォルダを確認したところ、 'x.xml'というファイルがありました。 XMLHttpRequest 2ファイルの拡張子を忘れた場合でも、それはそれを見つけるはずです。私は大好きです
同期ファイルを読む
しないでください。
しばらくの間ブラウザをブロックしたい場合は、Nice big .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');
これを非同期ではない方法で実行する方法は他にありません。 (ええ、setTimeoutループで...しかし真剣に?)
もう1つのポイントは、APIを使用している場合、または自分のリストのファイルだけを使用している場合、または要求ごとに常に異なる機能を使用している場合は...
常に同じXML/JSONをロードするページ、または必要な関数が1つだけのページがある場合に限ります。その場合は、Ajax関数を少し修正してbをあなたの特別な関数に置き換えてください。
上記の機能は基本的な用途のためのものです。
機能を拡張したい場合は.
はい、できます。
私はたくさんのAPIを使用していて、すべてのHTMLページに最初に統合した関数の1つは、この回答では最初のAjax関数です。GETのみです。
しかし、XMLHttpRequest 2では多くのことができます。
私はダウンロードマネージャ(resume、filereader、filesystemで両側の範囲を使う)、キャンバスを使ったさまざまな画像リサイズコンバータ、base64imagesを使ったWeb SQLデータベースの作成などを行いました。目的...時々、BLOB、配列バッファ、ヘッダーの設定、MIMEタイプの上書きが可能です。
しかし、ここでの質問はAjaxの応答を返す方法です(私は簡単な方法を追加しました)。
これは、AngularJS、jQuery(遅延)、ネイティブXHRの置換(フェッチ)、EmberJS、BackboneJSの保存、またはプロミスを返すノードライブラリを意味します。
あなたのコードはこれに沿ったものでなければなりません:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Felix Klingは、AJAXのコールバックを使用してjQueryを使用しているユーザー向けの回答を作成してきました。ネイティブXHRに対する回答があります。この答えは、フロントエンドまたはバックエンドでのプロミスの一般的な使用法です。
NodeJS/io.jsを使用したブラウザーおよびサーバーでのJavaScript同時実行モデルは、asynchronousおよびreactiveです。
Promiseを返すメソッドを呼び出すたびに、then
ハンドラーはalways非同期に実行されます-つまり、after.then
ハンドラー内にない、それらの下のコード。
これは、data
を返しているときに、定義したthen
ハンドラーがまだ実行されていないことを意味します。これは、返される値が時間内に正しい値に設定されていないことを意味します。
問題の簡単な例えを以下に示します。
function getFive(){
var data;
setTimeout(function(){ // set a timer for one second in the future
data = 5; // after a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
data
の値は、data = 5
部分がまだ実行されていないため、undefined
です。おそらくすぐに実行されますが、その時点では戻り値とは無関係です。
操作はまだ行われていないため(AJAX、サーバー呼び出し、IO、タイマー)、リクエストがコードにその値を伝える機会を得る前に値を返しています。
この問題の解決策の1つは、re-activelyをコーディングして、計算が完了したときにプログラムに何をするかを伝えることです。約束は、本質的に時間的(時間に敏感)であることにより、これを積極的に可能にします。
Promiseは時間の経過値です。プロミスには状態があり、値なしで保留中として開始され、次のように解決できます。
約束は、状態を変更することしかできませんonceその後、約束は常に同じ状態に永遠にとどまります。 then
ハンドラーをプロミスに添付して、その値を抽出し、エラーを処理できます。 then
ハンドラーは、呼び出しの 連鎖 を許可します。約束は それらを返すAPIを使用 によって作成されます。たとえば、最新のAJAX置換fetch
またはjQueryの$.get
は、promiseを返します。
Promiseで.then
を呼び出し、returnから何かを取得すると、処理された値のpromiseを取得します。別の約束を返せば素晴らしいものが手に入りますが、馬を保持しましょう。
上記の問題をPromiseで解決する方法を見てみましょう。最初に、遅延関数を作成するために Promiseコンストラクター を使用して、上からの約束状態の理解を示しましょう。
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
これで、promiseを使用するようにsetTimeoutを変換した後、then
を使用してカウントできます。
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we're RETURNING the promise, remember, a promise is a wrapper over our value
return delay(100).then(function(){ // when the promise is ready
return 5; // return the value 5, promises are all about return values
})
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
基本的に、valueを返す代わりに、並行性モデルのためにできません-可能な値に対してwrapperを返しますnwrap with then
。 then
で開くことができる箱のようなものです。
これは元のAPI呼び出しでも同じです。次のことができます。
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
したがって、これも同様に機能します。既に非同期の呼び出しから値を返すことはできないことを学びましたが、プロミスを使用して処理を実行するためにそれらをチェーンすることができます。非同期呼び出しから応答を返す方法がわかりました。
ES6には generators が導入されています。これは、途中で戻り、元の位置に戻ることができる関数です。これは通常、シーケンスに役立ちます。たとえば、次のとおりです。
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
反復可能なシーケンス1,2,3,3,3,3,....
に対してiteratorを返す関数です。これはそれ自体が興味深いものであり、多くの可能性の余地がありますが、興味深いケースが1つあります。
生成するシーケンスが数字ではなくアクションのシーケンスである場合、アクションが生成されるたびに関数を一時停止し、関数を再開する前に待機できます。したがって、一連の数字の代わりに、一連のfuture値-つまり:promisesが必要です。
このややトリッキーですが、非常に強力なトリックにより、非同期コードを同期的に記述できます。これを行う「ランナー」がいくつかあります。1つを書くことはコードの短い数行ですが、この答えの範囲を超えています。ここではBluebirdのPromise.coroutine
を使用しますが、co
やQ.async
などのラッパーもあります。
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
このメソッドはプロミス自体を返します。これは他のコルーチンから消費できます。例えば:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
console.log(baz); // runs after both requests done
});
main();
ES7では、これはさらに標準化されており、現在いくつかの提案がありますが、それらすべてでawait
を約束できます。これは、async
およびawait
キーワードを追加することによる、上記のES6提案の単なる「シュガー」(より優れた構文)です。上記の例を作成します。
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
まだ同じ約束を返します:)
あなたはAjaxを間違って使っています。これは何も返さないようにするのではなく、データを処理するコールバック関数と呼ばれるものにデータを渡すことです。
あれは:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
送信ハンドラで何かを返しても何も起こりません。代わりに、データを引き渡すか、またはsuccess関数内で直接データを使って必要なことをする必要があります。
最も簡単な解決策は、JavaScript関数を作成し、それをAjaxのsuccess
コールバックのために呼び出すことです。
function callServerAsync(){
$.ajax({
url: '...',
success: function(response) {
successCallback(response);
}
});
}
function successCallback(responseObj){
// Do something like read the response and show data
alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}
function foo(callback) {
$.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
AngularJS を使用している人は、Promises
を使用してこの状況を処理できます。
ここ と言う、
Promiseは非同期関数のネストを解除するために使用でき、複数の関数を連結できます。
素敵な説明を見つけることができます こちら も。
下記の docs にある例。
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
Angular2
では、次の例を見てください。ただし、 推奨 は、Angular2
でObservables
を使用します。
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
このようにして消費できます
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
original の投稿を参照してください。ただし、TypeScriptは native es6 Promises をサポートしていません。使用する場合は、そのためのプラグインが必要になる場合があります。
さらに、ここに約束 spec ここで定義します。
ここでの答えの大部分は、単一の非同期操作があるときの役に立つ提案を与えます、しかし時々、これは配列または他のリストのような構造の each エントリに非同期操作をする必要があるときに起こります。誘惑はこれをすることです:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.Push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
例:
// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.Push(result);
});
});
console.log("Results:", results); // E.g., using them, returning them, etc.
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
うまくいかないのは、doSomethingAsync
からのコールバックが、結果を使用しようとしている時点でまだ実行されていないためです。
そのため、配列(またはある種のリスト)があり、各エントリに対して非同期操作を実行する場合は、2つの選択肢があります。操作を並列(重複)または直列(順番に)に実行する。
あなたはそれら全てを始めて、あなたが期待しているコールバックの数を追跡し、そしてその数のコールバックを得た時に結果を使うことができます。
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
例:
var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(expecting
を廃止してresults.length === theArray.length
を使用することもできますが、呼び出しが未解決の間にtheArray
が変更される可能性があります...)
結果が順序どおりに到着しなくても、関連するエントリと同じ位置にindex
のforEach
を使用して結果をresults
に保存する方法に注意してください(非同期呼び出しは必ずしも開始された順序で完了するとは限らないため)。 ).
しかし、もしあなたが return にする必要があるのなら、どうすればいいでしょうか。他の答えが指摘したように、あなたはできません。あなたの関数にコールバックを受け入れてコールさせる(あるいは Promise を返す)必要があります。これはコールバックバージョンです:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
あるいは、これは代わりにPromise
を返すバージョンです。
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
もちろん、doSomethingAsync
がエラーを渡した場合は、エラーが発生したときに約束を拒否するためにreject
を使用します。)
例:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(または、約束を返すdoSomethingAsync
のラッパーを作成してから、以下の手順を実行することもできます。)
doSomethingAsync
が Promise を与える場合は、 Promise.all
を使用できます。
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
doSomethingAsync
が2番目と3番目の引数を無視することがわかっている場合は、それをmap
に直接渡すことができます(map
は3つの引数でコールバックを呼び出しますが、ほとんどの人は最初の引数だけを使用します)。
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Promise.all
は、すべて解決されたときに指定したすべての約束の結果の配列でその約束を解決するか、または拒否したときに first が指定されたときにその約束を拒否します。
操作を並列にしたくないとします。順番に実行したい場合は、次の操作を始める前に各操作が完了するのを待つ必要があります。これがそれを行い、結果を使ってコールバックを呼び出す関数の例です。
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.Push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
(順番に作業を行っているので、結果が順不同にならないことがわかっているので、results.Push(result)
を使用することができます。上記ではresults[index] = result;
を使用した可能性がありますが、以下の例では使用しません。使用するインデックスがありません。)
例:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.Push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(または、doSomethingAsync
のラッパーを作成して約束を与え、以下を実行してください...)
doSomethingAsync
でPromiseが得られる場合、ES2017 +の構文(おそらく Babel のようなtranspiler)を使用できれば、 for-of
および_と共に async
function を使用できます。 await
:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.Push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
例:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.Push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
まだES2017 +構文を使用できない場合は、 "Promise reduce"パターン のバリエーションを使用できます(これは通常のPromise reduceよりも複雑です。次のものに、しかし代わりに配列でそれらの結果を集める:
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.Push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.Push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
...これは、 ES2015 +矢印機能 を使用した場合、あまり面倒ではありません。
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.Push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.Push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
この例を見てください。
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
ご覧のようにgetJoke
はaを返す resolve promiseです(res.data.value
を返すときに解決されます)。そのため、$ http.get要求が完了してからconsole.log(res.joke)が実行されるまで待機します(通常の非同期フローとして)。
これはplnkrです。
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/ /
ES6の方法(非同期 - 待機)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
これは、多くの新しいJavaScriptフレームワークで使用されているtwo way data bindingが最適な場所の1つです...
Angular、Reactまたはtwo way data bindingまたはstore conceptを実行する他のフレームワークを使用している場合、この問題は単純に修正されます。簡単なWordでは、結果は最初の段階でundefined
であるため、データを受け取る前にresult = undefined
を取得しているため、結果を取得するとすぐに更新され、割り当てられますAjaxの応答が呼び出す新しい値...
しかし、たとえばこの質問で尋ねたように、純粋なjavascriptまたはjQueryでどのように行うことができますか?
callback、promise、最近ではobservableを使用して処理できます。たとえば、promiseではsuccess()
やthen()
のような関数がありますコールバックまたはsubscribe function on observableと同様に、データの準備ができたときに実行されます。
たとえば、jQueryを使用している場合、次のようなことができます。
$(document).ready(function(){
function foo() {
$.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
promisesおよびobservablesの詳細については、これらの非同期処理を行う新しい方法です。
非同期関数から値を返すもう1つの方法は、非同期関数からの結果を格納するオブジェクトを渡すことです。
これが同じ例です。
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
非同期操作中に値を格納するためにresult
オブジェクトを使用しています。これにより、非同期ジョブの後でも結果を利用できるようになります。
私はこのアプローチをよく使います。私は、このアプローチが、連続したモジュールを介して結果を配線することがどのようにうまくいっているかを知りたいと思います。
プロミスとコールバックは多くの状況でうまく機能しますが、次のように表現するのは後部の苦痛です。
if (!name) {
name = async1();
}
async2(name);
async1
を通過することになります。 name
が未定義かどうかを確認し、それに応じてコールバックを呼び出します。
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
小さな例では okay ですが、似たようなケースやエラー処理がたくさんあるといらいらします。
Fibers
は問題解決に役立ちます。
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
プロジェクトをチェックアウトできます ここ 。
簡単な答えは、 このようなコールバックを実装する必要があります。
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
私が書いた次の例は、その方法を示しています。
この作業例は自己完結型です。それは呼び出しをするためにウィンドウXMLHttpRequest
オブジェクトを使う単純なrequestオブジェクトを定義します。それは約束の束が完了するのを待つための簡単な関数を定義します。
コンテキスト。例は、 Spotify Web API エンドポイントにクエリ文字列のplaylist
オブジェクトを検索するためのクエリです。
[
"search?type=playlist&q=%22Doom%20metal%22",
"search?type=playlist&q=Adele"
]
アイテムごとに、新しいPromiseがブロックを起動します - ExecutionBlock
、結果を解析し、結果の配列に基づいて新しいpromiseのセット、すなわちSpotifyのuser
オブジェクトのリストをスケジュールし、ExecutionProfileBlock
内で新しいHTTP呼び出しを非同期に実行します。
これで、ネストされたPromise構造を見ることができます。これにより、複数の完全に非同期のネストされたHTTP呼び出しを生成し、呼び出しの各サブセットからの結果をPromise.all
を通じて結合できます。
_ note _ 最近のSpotify search
APIでは、リクエストヘッダにアクセストークンを指定する必要があります。
-H "Authorization: Bearer {your access token}"
そのため、次の例を実行するには、アクセストークンを要求ヘッダーに含める必要があります。
var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
call: function(what, response) {
var request;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // Internet Explorer
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
// State changes
request.onreadystatechange = function() {
if (request.readyState === 4) { // Done
if (request.status === 200) { // Complete
response(request.responseText)
}
else
response();
}
}
request.open('GET', what, true);
request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
request.send(null);
}
}
//PromiseAll
var promiseAll = function(items, block, done, fail) {
var self = this;
var promises = [],
index = 0;
items.forEach(function(item) {
promises.Push(function(item, i) {
return new Promise(function(resolve, reject) {
if (block) {
block.apply(this, [item, index, resolve, reject]);
}
});
}(item, ++index))
});
Promise.all(promises).then(function AcceptHandler(results) {
if (done) done(results);
}, function ErrorHandler(error) {
if (fail) fail(error);
});
}; //promiseAll
// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
var url = "https://api.spotify.com/v1/"
url += item;
console.log( url )
SimpleRequest.call(url, function(result) {
if (result) {
var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
return item.owner.href;
})
resolve(profileUrls);
}
else {
reject(new Error("call error"));
}
})
}
arr = [
"search?type=playlist&q=%22Doom%20metal%22",
"search?type=playlist&q=Adele"
]
promiseAll(arr, function(item, index, resolve, reject) {
console.log("Making request [" + index + "]")
ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results
console.log("All profiles received " + results.length);
//console.log(JSON.stringify(results[0], null, 2));
///// promiseall again
var ExecutionProfileBlock = function(item, index, resolve, reject) {
SimpleRequest.call(item, function(result) {
if (result) {
var obj = JSON.parse(result);
resolve({
name: obj.display_name,
followers: obj.followers.total,
url: obj.href
});
} //result
})
} //ExecutionProfileBlock
promiseAll(results[0], function(item, index, resolve, reject) {
//console.log("Making request [" + index + "] " + item)
ExecutionProfileBlock(item, index, resolve, reject);
}, function(results) { // aggregated results
console.log("All response received " + results.length);
console.log(JSON.stringify(results, null, 2));
}
, function(error) { // Error
console.log(error);
})
/////
},
function(error) { // Error
console.log(error);
});
<div id="console" />
私はこの解決策について広く議論しました ここ 。
これはとても簡単です。
これがあなたのコードの実用的なバージョンです:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
このカスタムライブラリ(Promiseを使って書かれたもの)を使ってリモート呼び出しをすることができます。
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
簡単な使用例
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
Jsはシングルスレッドです。
ブラウザは3つの部分に分けられます。
1)イベントループ
2)Web API
3)イベントキュー
イベントループは永遠に、つまり無限ループのように実行されます。イベントキューとはあなたのすべての関数があるイベントにプッシュされる場所です(例:クリック)これはキューから一つずつ実行されイベントループに置かれます。これは、ある関数の実行がキューに入る前の関数がイベントループで実行されるまで開始されないことを意味します。
2つの関数をキューにプッシュしたとしましょう。1つはサーバーからデータを取得するためのもので、もう1つはそのデータを利用するものです。 serverRequest関数はイベントループに入り、サーバーからデータを取得するのにかかる時間がわからないのでサーバーを呼び出します。この処理には時間がかかると予想されます。 APIが役割を果たすのは、イベントループからこの関数を取得し、キューから次の関数を実行できるようにイベントループを解放するサーバーを処理することです。キュー内の次の関数はループに入るutiliseData()ですが、データがないため次の関数の無駄と実行はキューの最後まで続きます(これは非同期呼び出しと呼ばれます。つまり、データを取得するまで他のことができます)
サーバーWeb APIからデータを取り戻すときに、serverRequest()関数にコード内にreturnステートメントがあるとします。キューの最後にキューに入れます。このデータを利用するための関数がキューに残っていないため、キューの最後にプッシュされるため、データを利用できません。したがって、Async Callから何かを返すことはできません。
したがって、これに対する解決策はコールバックまたは約束です。
ここの答えの1つからの画像。コールバックの使用法を正しく説明しています...私たちの関数(サーバーから返されたデータを利用する関数)を関数呼び出しサーバーに渡します。
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
私のコードでは、それはと呼ばれています
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
非同期呼び出しを行うためのECMA(2016/17)の新しい方法については、こちらを参照してください(@Felix Kling Answerの上) https://stackoverflow.com/a/14220323/7579856
別の解決策は、シーケンシャルエグゼキュータ nsynjs を介してコードを実行することです。
nsynjsはすべての約束を順番に評価し、約束の結果をdata
プロパティに入れます。
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
ステップ1. nsynjs対応のラッパーにコールバックを使用して関数をラップします(バージョンが約束されている場合は、このステップをスキップできます)。
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
ステップ2.同期ロジックを機能させる
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
ステップ3. nsynjsを介して関数を同期的に実行します。
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjsはすべての演算子と式を段階的に評価し、遅い関数の結果が準備できていない場合に備えて実行を一時停止します。
より多くの例はここにあります: https://github.com/amaksr/nsynjs/tree/master/examples
JavaScriptの「謎」に苦しんでいる間に私たちが直面する非常に一般的な問題です。今日、この謎を解明してみましょう。
簡単なJavaScript関数から始めましょう。
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
これは単純な同期関数呼び出し(コードの各行が順番に次の行の前に「そのジョブで終了する」)であり、結果は予想どおりです。
それでは、コードのすべての行が順番に「終了」しないように、関数に少しの遅延を導入して、少し工夫を加えましょう。したがって、これは関数の非同期動作をエミュレートします。
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
それで、あなたはそこへ行きます、その遅れはちょうど我々が期待する機能性を壊しました!しかし、正確にはどうなりましたか?コードを見れば、実はかなり論理的です。関数foo()
は、実行時に何も返しません(したがって、戻り値はundefined
です)が、タイマーを開始し、1秒後に関数を実行して 'wohoo'を返します。しかし、ご覧のとおり、barに割り当てられている値は、foo()からすぐに返されたものであり、後でくるものではありません。
では、この問題にどう対処しますか?
私たちの関数にPROMISEを依頼しましょう。 Promiseは本当にそれが何を意味するのかについてのものです:それは関数が将来得られるどんな出力でも提供することを関数が保証することを意味します。それでは、上記のちょっとした問題に対する実際の動作を見てみましょう。
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
したがって、要約すると、ajaxベースの呼び出しなどの非同期関数に取り組むために、(返す予定の)値をresolve
にすることができます。つまり、非同期関数では、要するにreturnの代わりにresolve valueを使用します。
then/catch
を使用して約束を処理することとは別に、もう1つアプローチがあります。考えは 非同期関数を認識することです そしてそれから コードが次の行に進む前に解決するために を待ちます。それはまだ根底にあるpromises
ですが、構文上のアプローチは異なります。物事を明確にするために、あなたは以下の比較を見つけることができます:
function fetchUsers(){
let users = [];
getUsers()
.then(_users => users = _users)
.catch(err =>{
throw err
})
return users;
}
async function fetchUsers(){
try{
let users = await getUsers()
return users;
}
catch(err){
throw err;
}
}
ECMAScript 6には、非同期スタイルで簡単にプログラムできる「ジェネレータ」があります。
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
上記のコードを実行するには、次のようにします。
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
ES6をサポートしていないブラウザをターゲットにする必要がある場合は、Babelまたはクロージャコンパイラを介してコードを実行し、ECMAScript 5を生成できます。
コールバック...args
は配列にラップされ、それらを読むときに逆構造化されるので、パターンは複数の引数を持つコールバックに対処することができます。例えば node fs の場合:
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
var App = App || {};
App = {
getDataFromServer: function(){
var self = this,
deferred = $.Deferred(),
requests = [];
requests.Push($.getJSON('request/ajax/url/1'));
requests.Push($.getJSON('request/ajax/url/2'));
$.when.apply(jQuery, requests).done(function(xhrResponse) {
return deferred.resolve(xhrResponse.result);
});
return deferred;
},
init: function(){
this.getDataFromServer().done(_.bind(function(resp1, resp2) {
// Do the operations which you wanted to do when you
// get a response from Ajax, for example, log response.
}, this));
}
};
App.init();
簡単な答え :あなたのfoo()
メソッドはすぐに戻りますが、$ajax()
呼び出しは非同期に を実行した後、関数は を返します。問題は、非同期呼び出しが取得した結果をどのようにまたはどこに格納するかです。
このスレッドではいくつかの解決策が提供されています。おそらく最も簡単な方法は、オブジェクトをfoo()
メソッドに渡して、非同期呼び出しが完了した後でそのオブジェクトのメンバーに結果を格納することです。
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
foo()
への呼び出しはまだ有用なものを何も返さないことに注意してください。ただし、非同期呼び出しの結果はresult.response
に格納されるようになりました。
callback()
成功の中でfoo()
関数を使用してください。このようにしてみてください。シンプルでわかりやすいです。
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
私たちは自分たちが「時間」と呼ぶ次元に沿って進歩しているように見える宇宙にいることに気づきます。私たちは今何時なのかはよくわかりませんが、それを推論して話すための抽象化と語彙を開発しました: "過去"、 "現在"、 "未来"、 "前"、 "後"。
私たちが構築しているコンピューターシステムは、ますます重要な次元としての時間があります。将来起こることがいくつかあります。それからそれらの最初の事が最終的に起こった後に他のことが起こる必要があります。これが「非同期性」と呼ばれる基本概念です。私たちのますますネットワーク化された世界では、非同期性の最も一般的なケースはあるリモートシステムがある要求に応答するのを待っていることです。
例を考えてみましょう。あなたは牛乳配達人に電話して牛乳を注文します。それが来たら、あなたはあなたのコーヒーに入れたいのです。まだここにないので、あなたは今あなたのコーヒーにミルクを入れることができません。あなたはそれをあなたのコーヒーに入れる前に来るのを待たなければなりません。言い換えれば、以下はうまくいきません。
var milk = order_milk();
put_in_coffee(milk);
なぜならJSはorder_milk
を実行する前にput_in_coffee
を終了させるために wait が必要であることを知る方法がないからです。言い換えれば、order_milk
が 非同期 - であることが、将来までミルクにならないということを知りません。 JSや他の宣言型言語は、待たずに次々にステートメントを実行します。
この問題に対する古典的なJSのアプローチは、JSが受け渡し可能なファーストクラスのオブジェクトとして関数をサポートするという事実を利用して、非同期要求にパラメータとして関数を渡し、それが完了すると呼び出されることです。将来そのタスクそれが「コールバック」アプローチです。それはこのように見えます:
order_milk(put_in_coffee);
order_milk
はキックオフし、ミルクを注文し、そしてそれが到着したときにだけ、それはput_in_coffee
を呼び出します。
このコールバックアプローチの問題は、それがreturn
で結果を報告する関数の通常の意味論を汚染することです。代わりに、関数はパラメータとして与えられたコールバックを呼び出すことによってそれらの結果を報告してはいけません。また、このアプローチは、より長い一連のイベントを処理するときに急速に扱いにくくなる可能性があります。たとえば、牛乳がコーヒーに入れられるのを待ってから、その後になって初めてコーヒーを飲むという3番目のステップを実行したいとします。私は結局このようなものを書く必要があります:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
ここで私はput_in_coffee
にそれを入れるミルクと、ミルクを入れた後に実行するアクション(drink_coffee
)の両方を渡しています。
この場合、質問のコードを次のように書き換えることができます。
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
これが「約束」という概念の動機でした。これは、ある種の 未来 または 非同期 の結果を表す特定のタイプの値です。それはすでに起こったこと、将来起こること、あるいは決して起こらないことを表すことができます。 promiseにはthen
という名前の単一のメソッドがあり、それにはpromiseが表す結果が実現されたときに実行されるアクションを渡します。
私たちのミルクとコーヒーの場合、私たちはミルクが到着するという約束を返すようにorder_milk
を設計し、それからput_in_coffee
をthen
アクションとして次のように指定します。
order_milk() . then(put_in_coffee)
この利点の1つは、これらを一緒に並べて将来の出現のシーケンスを作成できることです(「連鎖」)。
order_milk() . then(put_in_coffee) . then(drink_coffee)
あなたの特定の問題に約束を適用しましょう。リクエストロジックを関数内にラップします。これは約束を返します。
function get_data() {
return $.ajax('/foo.json');
}
実際には、$.ajax
の呼び出しにreturn
を追加しただけです。これは、jQueryの$.ajax
がすでに一種の約束のようなものを返すために機能します。 (実際には、詳細に入らずに、この呼び出しをラップして実際の約束を返すか、$.ajax
の代わりにそれを使用することをお勧めします)。それから何かをする、私たちは単に言うことができます
get_data() . then(do_something)
例えば、
get_data() .
then(function(data) { console.log(data); });
Promiseを使うとき、then
にたくさんの関数を渡すことになるので、よりコンパクトなES6スタイルのarrow関数を使うことはしばしば役に立ちます:
get_data() .
then(data => console.log(data));
async
キーワードしかし、同期の場合は一方向に、非同期の場合はまったく別の方法でコードを記述する必要があることについては、まだ漠然とした不満があります。同期のために、書く
a();
b();
しかしa
が非同期の場合は、約束をもって書く必要があります。
a() . then(b);
上で、私たちは言った、「JSには、2番目の呼び出しを実行する前に最初の呼び出しを終了するために wait が必要であることを知る方法がありません」。 が であるとJSに通知する方法があればいいでしょうか。 "async"関数と呼ばれる特別なタイプの関数内で使用されるawait
キーワードがあります。この機能はESの次期バージョンの一部ですが、正しいプリセットを与えられたBabelのようなトランスパイラーですでに利用可能です。これは私達が単に書くことを可能にします
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
あなたの場合は、次のように書くことができます。
async function foo() {
data = await get_data();
console.log(data);
}
もちろん、同期要求、約束のような多くのアプローチがあります、しかし私の経験から私はあなたがコールバックアプローチを使うべきだと思います。 Javascriptの非同期動作には当然のことです。だから、あなたのコードスニペットは少し異なる書き直すことができます:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
質問は次のとおりです。
非同期呼び出しから応答を返すにはどうすればいいですか?
これは次のように解釈できます。
非同期コードを同期にするにはどうすればよいですか?
解決策は、コールバックを回避し、Promisesとasync/awaitの組み合わせを使用することです。
Ajaxリクエストの例を挙げたいと思います。
(Javascriptで記述することもできますが、Pythonで記述し、 Transcrypt を使用してJavascriptにコンパイルすることをお勧めします。これは十分に明らかです。)
最初にJQueryの使用を有効にして、$
をS
として使用可能にします。
__pragma__ ('alias', 'S', '$')
Promiseを返す関数、この場合はAjax呼び出しを定義します。
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
非同期コードを同期のように使用します。
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
ES2017を使用すると、関数宣言としてこれを持っているはずです
async function foo() {
var response = await $.ajax({url: '...'})
return response;
}
そしてそれをこのように実行します。
(async function() {
try {
var result = await foo()
console.log(result)
} catch (e) {}
})()
あるいはPromiseの構文
foo().then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
コードを投げかけるのではなく、JSがコールバックと非同期性をどのように処理するかを理解するための鍵となる2つの概念があります。 (それも一言ですか?)
注意すべき点が3つあります。 キュー。 イベントループ とスタック
簡単に言えば、イベントループはプロジェクトマネージャのようなもので、実行したい機能やキューとスタック間の通信を常に待機しています。
while (queue.waitForMessage()) {
queue.processNextMessage();
}
何かを実行するメッセージを受け取ると、それをキューに追加します。キューは実行を待っているもののリストです(あなたのAJAX requestのように)。このように想像してみてください。
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
これらのメッセージの1つが実行しようとすると、キューからメッセージをポップしてスタックを作成します。スタックは、メッセージ内の命令を実行するためにJSが実行する必要があるものすべてです。だから私たちの例ではfoobarFunc
を呼ぶように言われています
function foobarFunc (var) {
console.log(anotherFunction(var));
}
そのため、foobarFuncが実行する必要があるもの(この場合はanotherFunction
)がスタックにプッシュされます。実行され、そして忘れられた - イベントループはキューの次のものに移動します(あるいはメッセージを待ち受けます)
ここで重要なことは実行順序です。それは
AJAXを使用して外部の相手に電話をかける場合、または非同期コード(setTimeoutなど)を実行する場合、Javascriptは処理を進める前に応答に依存します。
大きな問題は、いつ応答が得られるのかということです。答えはわれわれにはわかりません - そのため、イベントループはそのメッセージが "hey run me"と言ってくるのを待っています。 JSがそのメッセージを同期的に待っていただけでは、アプリはフリーズしてしまいます。そのため、JSはメッセージがキューに追加されるのを待っている間、キュー内の次の項目の実行を続けます。
だからこそ、非同期機能では callbacks と呼ばれるものを使います。それはちょっと 約束 /まったく文字通りのようです。 I ある時点で何かを返すことをお約束します jQueryは、(とりわけ)deffered.done
、deffered.fail
、およびdeffered.always
という特定のコールバックを使用します。全部見ることができます ここ
だからあなたがする必要があるのはそれに渡されるデータである時点で実行することが約束されている関数を渡すことです。
コールバックはすぐには実行されませんが、後で実行されるのではなく、関数への参照を渡すことが重要です。そう
function foo(bla) {
console.log(bla)
}
そのため、ほとんどの場合(常にではありません)、foo()
ではなくfoo
を渡すことになります。
うまくいけば、それはいくつかの意味があります。あなたがこのような混乱を招くようなことに遭遇したとき - 私は少なくともそれを理解するために完全に文書を読むことを強く勧めます。それはあなたをずっと良い開発者にするでしょう。
木を見る前に、まず森を見ましょう。
ここには詳細な情報が含まれた有益な回答がたくさんありますが、ここでは繰り返さないことにします。 JavaScriptでプログラミングするための鍵は、最初に 正しい精神モデル 全体的な実行の - を持つことです。
良い点は、この点をよく理解していれば、競合状態について心配する必要がなくなることです。どのようにしてコードを本質的に異なる個別のイベントに対する応答として編成するか、そしてそれらをどのように論理的なシーケンスにスレッド化するかについて、まず第一に考えてください。そのためのツールとして、promiseまたは高レベルの新しいasync/awaitを使用することも、自分でロールバックすることもできます。
しかし、実際の問題領域に慣れるまでは、問題を解決するための戦術的なツールを使用しないでください。いつ実行する必要があるのかを知るために、これらの依存関係のマップを描きます。これらすべてのコールバックに対してアドホックなアプローチを試みるだけではうまくいきません。
この質問に対する最も完璧な答えはPromise
を使うことです。
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
約束の使用に問題があります。
私は古いブラウザにエラーがあることがわかるまで、私はしばらくこの解決策を使っていました:
Uncaught ReferenceError: Promise is not defined
だから私は ES3のための私自身のPromiseクラスを jsコンパイラの下に定義するのでなければ実装することにした。メインコードの前にこのコードを追加してから、安全ユーザーのPromiseを追加してください。
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.Push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.Push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}
これはうまくいく例です:
const validateName = async userName => {
const url = "abc/xyz";
try {
const response = await axios.get(url);
return response.data
} catch (err) {
return false;
}
};
validateName("user")
.then(data => console.log(data))
.catch(reason => console.log(reason.message))
リクエストは非同期的に機能するため、通常のコードのように同期的にデータを読み取ることはできません。ただし、async/await
を使用すると、同期コードに近い/類似した非同期コードを作成できます。要求データを処理するコードは、非同期関数(以下のスニペットのload
)でラップする必要があり、その中にfoo()
(これもasync/await
を使用する)の前にawait
keywortを追加する必要があります)。
async function foo() {
var url= 'https://jsonplaceholder.typicode.com/todos/1';
var result= await (await fetch(url)).text(); // or .json()
return result;
}
async function load() {
var result = await foo();
console.log(result);
}
load();
babelなどのトランスパイラーでasync/await
を使用して、古いブラウザーで動作させる。また、このBabelプリセットとポリフィルをnpmからインストールする必要があります:npm i -D babel-preset-env babel-polyfill。
function getData(ajaxurl) {
return $.ajax({
url: ajaxurl,
type: 'GET',
});
};
async test() {
try {
const res = await getData('https://api.icndb.com/jokes/random')
console.log(res)
} catch(err) {
console.log(err);
}
}
test();
または.then
コールバックは、同じロジックを記述する別の方法です。
getData(ajaxurl).then(function(res) {
console.log(res)
}