非同期呼び出しから応答を返すにはどうすればよいですか?


5511

fooAjaxリクエストを行う機能を持っています。どのようにして応答を返すことができ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`.

回答:


5703

→さまざまな例での非同期動作のより一般的な説明について は、関数内で変数を変更した後、変数が変更されないのはなぜですかを参照してください。-非同期コードリファレンス

→すでに問題を理解している場合は、以下の可能な解決策に進んでください。

問題

AjaxA非同期を意味します。つまり、要求の送信(または応答の受信)は、通常の実行フローから除外されます。この例では、すぐに戻り、コールバックとして渡された関数が呼び出される前に次のステートメントが実行されます。$.ajaxreturn 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();

応答を待つのではなく、実行は直ちに続行され、Ajax呼び出しの後のステートメントが実行されます。最終的に応答を取得するには、応答が受信されたときに呼び出される関数、コールバック(何か通知?コールバック?)を提供します。その呼び出しの後に続くステートメントは、コールバックが呼び出される前に実行されます。


ソリューション

JavaScriptの非同期性を受け入れてください!特定の非同期操作は同期対応を提供します(「Ajax」も同様)。ただし、特にブラウザーのコンテキストでは、それらを使用することはお勧めしません。

なぜ悪いのですか?

JavaScriptはブラウザーのUIスレッドで実行され、長時間実行されるプロセスはUIをロックし、応答しなくなります。さらに、JavaScriptの実行時間には上限があり、ブラウザーは実行を続行するかどうかをユーザーに尋ねます。

これらはすべて、ユーザーエクスペリエンスが非常に悪いものです。ユーザーは、すべてが正常に機能しているかどうかを確認できません。さらに、接続が遅いユーザーにとっては、この影響はさらに大きくなります。

以下では、3つの異なるソリューションが互いに重なり合っていることを確認します。

  • 約束async/await(ES2017 +、トランスパイラーまたは再生器を使用する場合は古いブラウザーで使用可能)
  • コールバック(ノードで人気)
  • Promises withthen()(ES2015 +、多くのpromiseライブラリーのいずれかを使用する場合、古いブラウザーで使用可能)

3つすべては、現在のブラウザーとノード7以降で使用できます。


ES2017 +:約束 async/await

2017年にリリースされたECMAScriptバージョンでは、非同期関数の構文レベルのサポートが導入されました。助けを借りてasyncawait、あなたは「同期型」で非同期を書くことができます。コードはまだ非同期ですが、読みやすく、理解しやすくなっています。

async/awaitasyncpromiseの上に構築します。関数は常にpromiseを返します。awaitプロミスを「アンラップ」し、プロミスが解決された値になるか、プロミスが拒否された場合はエラーをスローします。

重要:関数await内でのみ使用できasyncます。現在、トップレベルawaitはまだサポートされていないため、コンテキストを開始するために非同期IIFE(即時に呼び出される関数式)を作成する必要がある場合がありasyncます。

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);
})();

現在のブラウザノードのバージョンがサポートしていasync/awaitます。ジェネレーター(またはBabelなどのジェネレーターを使用するツール)を使用してコードをES5に変換することにより、古い環境をサポートすることもできます。


関数にコールバックを受け入れさせる

コールバックは、別の関数に渡される関数です。その他の関数は、準備ができるといつでも渡された関数を呼び出すことができます。非同期プロセスのコンテキストでは、非同期プロセスが完了するたびにコールバックが呼び出されます。通常、結果はコールバックに渡されます。

質問の例ではfoo、コールバックを受け入れてコールバックとして使用できsuccessます。したがって、この

var result = foo();
// Code that depends on 'result'

なる

foo(function(result) {
    // Code that depends on 'result'
});

ここでは関数「インライン」を定義しましたが、任意の関数参照を渡すことができます。

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo それ自体は次のように定義されます。

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callbackfoo呼び出すときに渡す関数を参照し、単にそれをに渡しsuccessます。つまり、Ajaxリクエストが成功$.ajaxするとcallback、応答を呼び出してコールバックに渡します(これresultはコールバックの定義方法であるため、で参照できます)。

コールバックに渡す前に応答を処理することもできます。

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

見かけよりもコールバックを使用してコードを記述する方が簡単です。結局のところ、ブラウザーのJavaScriptは非常にイベント駆動型(DOMイベント)です。Ajax応答の受信は、イベントに他なりません。
サードパーティのコードを使用する必要がある場合に問題が発生する可能性がありますが、ほとんどの問題は、アプリケーションフローを検討するだけで解決できます。


ES2015 +:then()で約束

約束APIは、 ECMAScriptの6(ES2015)の新機能ですが、それは良い持っているブラウザのサポートをすでに。標準のPromises 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呼び出しに適用すると、次のようなpromiseを使用できます。

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のロック-JavaScriptプロミス

補足:jQueryの遅延オブジェクト

遅延オブジェクトは、jQueryのプロミスのカスタム実装です(Promise APIが標準化される前)。これらはpromiseとほとんど同じように動作しますが、多少異なるAPIを公開します。

jQueryのすべてのAjaxメソッドは、すでに「据え置きオブジェクト」(実際には据え置きオブジェクトの約束)を返します。これは、関数から返すことができます。

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

補足:約束の問題

promiseとdeferredオブジェクトは単なる将来の値のコンテナーであり、値そのものではないことに注意してください。たとえば、次のような場合を考えます。

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
});

非推奨:同期「Ajax」呼び出し

すでに述べたように、非同期操作の中には対応する同期操作があるものもあります(!)。私はそれらの使用を推奨しませんが、完全を期すために、同期呼び出しを実行する方法を次に示します。

jQueryなし

XMLHTTPRequestオブジェクトを直接使用する場合は、false3番目の引数としてに渡します.open

jQuery

jQueryを使用する場合は、asyncオプションをに設定できますfalse。このオプションはjQuery 1.8以降廃止されていることに注意してください。その後、successコールバックを使用するかresponseTextjqXHRオブジェクトのプロパティにアクセスできます

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

あなたのような、他のjQueryのAjaxメソッドを使用している場合$.get$.getJSONなど、あなたはそれを変更する必要が$.ajax(あなただけに設定パラメータを渡すことができるため$.ajax)。

注意喚起!同期JSONPリクエストを行うことはできません。JSONPはその性質上、常に非同期です(このオプションを考慮しない理由の1つです)。


74
@Pommy:jQueryを使用する場合は、jQueryを含める必要があります。docs.jquery.com/Tutorials:Getting_Started_with_jQueryを参照してください。
Felix Kling 2013年

11
ソリューション1のサブjQueryでは、この行を理解できませんでした:(If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.はい、この場合、ニックネームは
ちょっと

32
@gibberish:うーん、どうすればそれをより明確にすることができるのかわかりません。がどのようfooに呼び出され、関数がそれに渡されるのか(foo(function(result) {....});?)resultこの関数内で使用され、Ajaxリクエストの応答です。この関数を参照するには、無名関数の代わりにfooの最初のパラメーターを呼び出しcallbackて割り当てsuccessます。したがって、リクエストが成功したとき$.ajaxに呼び出されますcallback。もう少し説明してみました。
フェリックスクリング2013

43
この質問のチャットは終了しているため、変更の概要を提案する場所はわかりませんが、次のことを提案します。2)コールバックの例を削除/マージして、より柔軟なDeferredアプローチのみを示します。JavaScriptを学習している人にとっては、これは少し理解しやすいと思います。
Chris Moschini 2013

14
@ジェシー:私はあなたが答えのその部分を誤解したと思います。$.getJSONAjaxリクエストを同期させる場合は使用できません。ただし、リクエストが同期的であることをイベントで望むべきではないので、それは適用されません。応答の前半で説明されているように、応答を処理するにはコールバックまたはプロミスを使用する必要があります。
Felix Kling、2015年

1071

コードでjQueryを使用していない場合、この答えはあなたのためです

あなたのコードはこれに沿ったものでなければなりません:

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を使用している人のために答えを書いて素晴らしい仕事をしました、私はそうでない人のための代替を提供することにしました。

注、新しいfetchAPI、Angular、またはpromiseを使用している場合は、以下に別の回答を追加しました


あなたが直面しているもの

これは他の回答の「問題の説明」の短い要約です。これを読んでもわからない場合は、それを読んでください。

AJAX のA非同期の略です。つまり、要求の送信(または応答の受信)は、通常の実行フローから除外されます。この例で.sendは、すぐに戻りreturn result;successコールバックとして渡された関数が呼び出される前に次のステートメントが実行されます。

これは、戻るときに、定義したリスナーがまだ実行されていないことを意味します。つまり、返す値が定義されていません。

ここに簡単な類推があります

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(フィドル)

a戻り値はundefineda=5パーツがまだ実行されていないためです。AJAXはこのように動作し、サーバーがブラウザーにその値が何であるかを伝える機会を得る前に値を返します。

この問題に対する1つの可能な解決策は、コードを再アクティブにして、計算が完了したときに何をするかをプログラムに指示することです。

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」を警告する必要があります。(フィドル)

可能な解決策

これを解決するには、基本的に2つの方法があります。

  1. AJAX呼び出しを同期化します(これをSJAXと呼びます)。
  2. コールバックで適切に機能するようにコードを再構成します。

1.同期AJAX-しないでください!!

同期AJAXについて行わないでください!フェリックスの答えは、なぜそれが悪い考えであるかについていくつかの説得力のある議論を引き起こします。要約すると、サーバーが応答を返し、非常に悪いユーザーエクスペリエンスを作成するまで、ユーザーのブラウザーを凍結します。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);
}

2.コードを再構築する

関数にコールバックを受け入れさせます。この例でfooは、コールバックを受け入れるようにコードを作成できます。完了時にどのように反応するかをコードに伝えますfoo

そう:

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();
}

(フィドル)

これで、AJAXが正常に完了したときに実行するアクションをfoo関数が受け入れるようになりました。応答ステータスが200でないかどうかを確認し、それに応じて動作する(失敗ハンドラーなどを作成する)ことで、これをさらに拡張できます。問題を効果的に解決します。

それでも理解できない場合は、MDNのAJAX入門ガイドをお読みください


20
「同期リクエストはコードの実行をブロックし、メモリとイベントをリークする可能性があります」
Matthew G

10
@MatthewG私はこの質問にそれに賞金を追加しました、私は私が釣り出すことができるものを見ていきます。当面の間、私は回答から引用を取り除いています。
Benjamin Gruenbaum 2013

17
参考までに、XHR 2ではonloadハンドラーを使用できます。ハンドラーは、の場合にのみ起動しreadyStateます4。もちろん、IE8ではサポートされていません。(iirc、確認が必要な場合があります。)
Florian Margaine 2013

9
匿名関数をコールバックとして渡す方法の説明は有効ですが、誤解を招きます。例var bar = foo(); 変数を定義するように求めていますが、提案されたfoo(functim(){}); バーを定義しない
ロビーアベリル2014

396

XMLHttpRequest 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. 新しいオンロードを使用します(したがって、readystate &&ステータスを確認する必要はありません)。
  4. XMLHttpRequest 1を煩わしくする覚えていない状況が他にもいくつかあります。

この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);

ご覧のとおり、私は同期を実装していません...それは悪いことです。

とは言っても...簡単な方法でやってみませんか?


コメントで述べたように、エラー&&同期の使用は答えの要点を完全に壊します。適切な方法で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);

上記のスクリプトには、静的に定義されたエラーハンドラーがあるため、機能が損なわれることはありません。エラーハンドラは他の関数にも使用できます。

しかし、本当にエラーを発生させるには、すべてのブラウザがエラーをスローする場合、唯一の方法は間違ったURLを書き込むことです。

エラーハンドラーは、カスタムヘッダーを設定したり、responseTypeをblob配列バッファーなどに設定したりすると便利です。

メソッドとして 'POSTAPAPAP'を渡しても、エラーはスローされません。

'fdggdgilfdghfldj'をformdataとして渡しても、エラーはスローされません。

最初のケースでは、エラーはas のdisplayAjax()this.statusTextにありMethod not Allowedます。

2番目のケースでは、単純に機能します。正しい投稿データを渡したかどうかをサーバー側で確認する必要があります。

許可されていないクロスドメインは自動的にエラーをスローします。

エラー応答には、エラーコードはありません。

this.typeエラーに設定されているものだけがあります。

エラーを完全に制御できないのに、なぜエラーハンドラを追加するのですか?ほとんどのエラーは、コールバック関数でこの内部に返されますdisplayAjax()

したがって、URLを正しくコピーして貼り付けることができれば、エラーチェックは不要です。;)

PS:最初のテストとして、私は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');

これを非同期で行う方法は他にありません。(ええ、setTimeoutループで...しかし真剣に?)

もう1つのポイントは、APIや独自のリストのファイルを操作する場合など、リクエストごとに常に異なる関数を使用する場合です...

常に同じXML / JSONまたは1つの関数だけが必要なものをロードするページがある場合のみ。その場合は、Ajax関数を少し変更して、bを特別な関数に置き換えます。


上記の関数は基本的な使用のためのものです。

機能を拡張したい場合...

はい、できます。

私は多くのAPIを使用しています。すべてのHTMLページに統合する最初の関数の1つは、この回答の最初のAjax関数で、GETのみです...

しかし、XMLHttpRequest 2で多くのことができます。

ダウンロードマネージャー(履歴書、ファイルリーダー、ファイルシステムの両側で範囲を使用)、キャンバスを使用したさまざまな画像リサイズコンバーター、web SQLデータベースにbase64imagesなどを追加しましたが、これらの場合にのみ、そのための関数を作成します目的... BLOB、配列バッファーが必要な場合があります。ヘッダーを設定したり、mimetypeをオーバーライドしたりできます。

しかし、ここでの質問は、Ajax応答を返す方法です...(簡単な方法を追加しました)。


15
この答えは良いですが(XHR2が大好きで、ファイルデータとマルチパートデータを投稿するのはとても素晴らしいです)-これは、JavaScriptでXHRを投稿するための構文上の砂糖を示しています。またはライブラリにさえあります(名前がわからないxajaxまたはxhrもっといいかもしれません:))。AJAX呼び出しからの応答を返す方法がわかりません。(誰かがそれを行うことができvar res = x("url")、なぜそれが機能しないのか理解できません;))。サイドノートでは-あなたが返される場合、それはクールになるc方法から、ユーザーが上フックできるように、errorなど
ベンジャミンGruenbaum

25
2.ajax is meant to be async.. so NO var res=x('url')..それがこの質問と回答の要点です:)
Benjamin Gruenbaum 2013

3
関数に 'c'パラメータがあるのはなぜですか。最初の行で値が上書きされているのですか?私は何かを逃していますか?
Brian H.

2
パラメーターをプレースホルダーとして使用して、複数回「var」を書き込むことを回避できます
cocco

11
@coccoでは、いくつかのキーストロークを節約するために、SOの解答に誤解を招く、判読できないコードを記述しましたか?しないでください。

316

promiseを使用している場合、この答えはあなたのためです。

これは、AngularJS、jQuery(遅延)、ネイティブXHRの置換(フェッチ)、EmberJS、BackboneJSの保存、またはpromiseを返すノードライブラリを意味します。

あなたのコードはこれに沿ったものでなければなりません:

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並行性モデルは非同期反応します。

promiseを返すメソッドを呼び出すと、thenハンドラーは常に非同期で実行されます。つまり、ハンドラー内にないハンドラーの下のコードの後に実行されます.then

これは、定義しdatathenハンドラーを返すときにまだ実行されていないことを意味します。これは、返される値が時間内に正しい値に設定されていないことを意味します。

この問題の簡単な例を示します。

    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

の値dataundefineddata = 5パーツがまだ実行されていないためです。おそらく1秒で実行されますが、その時までには戻り値とは無関係です。

操作はまだ行われていないため(AJAX、サーバー呼び出し、IO、タイマー)、リクエストがコードにその値を伝える機会を得る前に値を返しています。

この問題に対する1つの可能な解決策は、コードを再アクティブにして、計算が完了したときに何をするかをプログラムに指示することです。プロミスは、本質的に一時的(時間依存)であることにより、これを積極的に可能にします。

約束の概要

約束は時間の経過に伴う価値です。プロミスには状態があり、値なしで保留中として開始し、次のように解決できます。

  • 満たされたという意味は、計算が正常に完了したことです。
  • 拒否は、計算が失敗したことを意味します。

約束は状態を一度だけ変更できその後は常に永遠に同じ状態に留まります。thenハンドラーをpromiseにアタッチして、値を抽出し、エラーを処理できます。thenハンドラーは呼び出しの連鎖を可能にします。プロミスは、プロミスを返すAPIを使用して作成されます。たとえば、より最近のAJAXの置き換えfetchやjQueryの$.getreturn promiseです。

.thenPromise を呼び出し、そこから何かを返す、処理された値の Promiseを取得します。もう一度約束を返せば素晴らしいものを手に入れられますが、馬を抱きましょう。

約束して

上記の問題を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);
    });
}

ここで、setTimeoutをpromiseを使用するように変換した後、を使用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;
});

基本的には、代わりに返すのために同時実行モデルの私たちが行うことはできません-私たちは戻っているラッパーたちができることを値のアンラップとを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`
})

したがって、これも同様に機能します。すでに非同期の呼び出しから値を返すことはできないことを学びましたが、promiseを使用してチェーン化し、処理を実行できます。これで、非同期呼び出しから応答を返す方法がわかりました。

ES2015(ES6)

ES6はジェネレーターを導入しています。ジェネレーターは、途中で戻って、ポイントを再開できる関数です。これは通常、たとえば次のようなシーケンスに役立ちます。

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,....。これはそれ自体が興味深いものであり、多くの可能性の余地を開いていますが、特定の興味深い事例が1つあります。

作成するシーケンスが数値ではなくアクションのシーケンスである場合、アクションが生成されるたびに関数を一時停止し、それを待ってから関数を再開できます。したがって、一連の数値の代わりに、一連の将来の値が必要です。つまり、約束です。

このいくらかトリッキーだが非常に強力なトリックにより、非同期コードを同期的に記述できます。これを行う「ランナー」がいくつかあります。1つを書くのは短いコード行ですが、この回答の範囲を超えています。Promise.coroutineここではBluebirdを使用しますが、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
});

このメソッドは他のコルーチンから消費できるpromise自体を返します。例えば:

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();

ES2016(ES7)

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
}

それでも同じようにpromiseを返します:)


これは受け入れられる答えになるはずです。async / awaitの+1(そうすべきではありませんreturn await data.json();か?)
Lewis Donovan

247

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関数内で直接、必要な処理を行う必要があります。


13
この答えは完全にセマンティックです...あなたの成功メソッドはコールバック内の単なるコールバックです。あなたはただ持っていてsuccess: handleData、それはうまくいくでしょう。
ジャックジャック

5
そして、もしあなたが "handleData"の外で "responseData"を返したいとしたらどうなるでしょう... :) ...どのようにそれを行うのでしょう...?...単純な戻りにより、それはajaxの "成功"コールバックに返されます... "handleData"の外ではありません...
pesho hristov

@ジャック&@pesho hristovこのポイントを逃しました。送信ハンドラはsuccessメソッドではなく、の周囲のスコープです$.ajax
travnik

@travnikお見逃しなく。handleDataのコンテンツを取得して成功メソッドに配置した場合、まったく同じように動作します...
ジャックジャック

234

最も簡単な解決策は、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);    
}); 

3
誰が反対票を投じたのかわかりません。しかし、これは実際に機能した回避策であり、私はこのアプローチを使用してアプリケーション全体を作成しました。jquery.ajaxはデータを返さないため、上記のアプローチを使用する方が適切です。それが間違っている場合は、それを説明するより良い方法を提案してください。
Hemant Bavle 2014年

11
コメントを残すのを忘れてしまいました(私はいつもそうです!)。反対票を投じました。反対票は、事実の正しさや欠如を示すものではなく、コンテキストでの有用性や欠如を示します。Felixがすでにこれをより詳細にしか説明していないので、あなたの答えは役に立たないと思います。余談ですが、JSONの場合、なぜ応答を文字列化するのですか?
Benjamin Gruenbaum 2014

5
わかりました。@ Benjamin私は、JSONオブジェクトを文字列に変換するためにstringifyを使用しました。そして、ポイントを明確にしてくれてありがとう。より手の込んだ回答を投稿することを覚えておいてください。
Hemant Bavle 2014

そして、もしあなたが "successCallback"の外で "responseObj"を返したいとしたら... :) ...どのようにそれを行うでしょう...?...単純に返すと、それはajaxの "success"コールバックに返されます... "successCallback"の外ではありません...
pesho hristov

221

恐ろしい手描きの漫画でお答えします。2番目の画像が理由ですresultundefinedコード例にあるです。

ここに画像の説明を入力してください


32
写真は1000ワードの価値があります人物A-人物Bの詳細に車の修理を依頼し、次に人物B -Ajax呼び出しを行い、車の修理の詳細についてサーバーからの応答を待ちます。応答を受信すると、Ajax成功関数が人物を呼び出しますBが機能し、その応答を引数として渡し、Person Aが応答を受け取ります。
shaijut 2016年

10
概念を説明するために、各画像にコード行を追加するとよいでしょう。
Hassan Baig 2018

1
その間、車を持った男は道端に立ち往生しています。続行する前に車を修理する必要があります。彼は今、道の脇で一人で待っています...彼はむしろ電話でステータスの変化を待っていますが、整備士はそれをしません...整備士は彼の仕事を続ける必要があり、できない単に電話に出かけるだけです。メカニックは、できるだけ早く彼にかけ直すと約束した。約4時間後、男はあきらめてUberに電話します。-タイムアウトの例。
バリーピッカー

@barrypicker :-Dブリリアント!
Johannes Fahrenkrug

159

角度1

AngularJSを使用している人は、この状況に対処することができますPromisesます。

ここで言う、

Promiseは非同期関数のネストを解除するために使用でき、複数の関数を一緒にチェーンすることができます。

ここで素敵な説明を見つけることができますもあります。

下記のドキュメントにある例。

  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、次の例を見てと、その推奨使用するObservablesAngular2

 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));
}

参照ここにポストを。ただし、Typescriptはネイティブes6 Promisesをサポートしていません、使用する場合は、プラグインが必要になる場合があります。

さらに、ここに定義されているpromise 仕様があります。


15
ただし、Promiseがこの問題をどのように解決するかは説明されていません。
Benjamin Gruenbaum 2014年

4
jQuery メソッドとfetchメソッドはどちらもpromise も返します。あなたの答えを修正することをお勧めします。jQueryはまったく同じではありませんが(そこにはありますが、catchは違います)。
Tracker1 '19

153

ここでの回答のほとんどは、単一の非同期操作がある場合に役立つ提案を提供しますが、配列または他のリストのような構造のエントリに対して非同期操作を実行する必要がある場合に、これが現れることがあります。誘惑はこれをすることです:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

例:

機能しない理由は、 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
        }
    });
});

例:

(をexpecting使用せずにそのまま使用することもできますresults.length === theArray.lengthが、これにより、theArray、呼び出しが未処理の間に変更さます...)

indexfrom を使用しforEachて結果を保存する方法に注意してくださいresults結果が順不同で到着した場合でも関連するエントリと同じ位置(非同期呼び出しは必ずしも開始された順序で完了するとは限らないため)。

しかし、関数からこれらの結果を返す必要がある場合はどうでしょうか?他の答えが指摘したように、あなたはできません。関数を受け入れてコールバックを呼び出す(または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);
});

例:

または、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は、エラーが発生したときにプロミスを拒否するためにします。)

例:

(またはdoSomethingAsync、約束を返すラッパーを作成してから、以下を実行することもできます...)

もしdoSomethingAsyncあなたに約束を与えるなら、あなたは使うことができますPromise.all

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

doSomethingAsync2番目と3番目の引数を無視することがわかっている場合は、それを直接渡すことができますmapmap3つの引数でコールバックを呼び出しますが、ほとんどの人は最初の引数のみを使用します)。

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

例:

Promise.allその約束をそれらが全て解決されたとき、あなたがそれを与える約束のすべての結果の配列との約束を解決する、または拒否したときに最初にあなたはそれを拒否与える約束の。

シリーズ

操作を並列にしたくない場合はどうでしょうか。これらを1つずつ実行する場合は、各操作が完了するのを待ってから、次の操作を開始する必要があります。これを実行して結果を伴うコールバックを呼び出す関数の例を次に示します。

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;、次の例の一部ではインデックスがありません。使用する。)

例:

(または、もう一度、ラッパーを作成しdoSomethingAsyncて約束を与えて、以下を実行します...)

場合はdoSomethingAsync、あなたに約束を与えるあなたは(おそらくのようなtranspilerとES2017 +構文を使用することができれば、バベル)は、使用することができますasync機能をfor-ofして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);
});

例:

ES2017 +構文を(まだ)使用できない場合は、「Promise削減」パターンのバリエーションを使用できます(これは通常のPromise削減よりも複雑です。なぜなら、結果を次の結果に渡さないためですが、代わりに結果を配列にまとめる):

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);
});

例:

... 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);
});

例:


1
if (--expecting === 0)コードの一部がどのように機能するか説明していただけますか?あなたのソリューションのコールバックバージョンは私にとって素晴らしい働きをしています。そのステートメントを使用して、完了した応答の数を確認する方法がわかりません。私の知識が不足しているだけだと感謝します。チェックを書くことができる別の方法はありますか?
サラ、

@Sarah:expectingの値から始まりますarray.length。これは、作成するリクエストの数です。これらのリクエストがすべて開始されるまで、コールバックは呼び出されません。コールバックでif (--expecting === 0)expecting、次のことを行います。1.減少(応答を受け取ったため、応答が1つ少ないことを期待しています)。減少の値が0(応答を期待していない)の場合、できた!
TJクラウダー2017年

1
@PatrickRoberts-ありがとう!! はい、コピーアンドペーストエラーです。この例では、2番目の引数が完全に無視されました(指摘したように、失敗しなかった唯一の理由resultsです。:-)修正しました。
TJクラウダー

111

この例を見てください:

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されて戻って解決の約束を(返すときに、それが解決されます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);
  });
})();

107

これは、多くの新しいJavaScriptフレームワークで使用される2つの方法のデータバインディングまたはストアコンセプトがうまく機能する場所の1つです...

したがって、Angular、React、またはデータバインディングまたはストアコンセプトの2つの方法を実行するその他のフレームワークを使用している場合、この問題は簡単に修正されるので、簡単に言えば、結果はundefined最初の段階にあり、result = undefined受け取る前に取得できます。データの場合、結果を取得するとすぐに更新され、Ajax呼び出しの応答である新しい値に割り当てられます...

しかし、たとえばこの質問で尋ねたように、純粋なjavascriptまたはjQueryでそれを行うにはどうすればよいでしょうか。

あなたは使用することができ、コールバック約束最近、観察を、私たちは次のようにいくつかの機能を持っている約束で、たとえば、あなたのためにそれを処理するために、success()またはthen()あなたのデータはあなたのための準備ができたときに実行される、コールバックと同じまたはサブスクライブに機能を観察できます

たとえば、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
});

詳細については、この非同期処理を行うための新しい方法であるプロミスオブザーバブルについて検討してください。


これは、グローバルスコープで結構ですが、あなたはおそらく、コールバックなどのための右のコンテキストを確実にしたいいくつかのモジュールのコンテキストで$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims

8
Reactは一方向のデータバインディングなので、これは実際には正しくありません
Matthew Brent

@MatthewBrent間違いではありませんが、正しくありません。Reactプロップはオブジェクトであり、変更された場合、アプリケーション全体で変更されますが、React開発者がそれを使用することを推奨する方法ではありません...
Alireza

98

これは、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()からすぐに返されたものundefinedです。

では、この問題にどのように取り組むのでしょうか。

関数に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、値(約束したもの)へのプロミスを使用できます。したがって、要するに、非同期関数でを返す代わりに値を解決します。

更新(非同期/待機での約束)

を使用then/catchしてpromise を処理する以外に、もう1つのアプローチがあります。アイデアがすることです非同期関数を認識して、約束を待つ解決するには、コードの次の行に移動する前に。それはまだpromises内部にあるだけですが、構文のアプローチが異なります。物事をより明確にするために、以下の比較を見つけることができます:

then / catchバージョン:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
          console.error(err);
       })
 }

非同期/待機バージョン:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }

これは、promiseまたはasync / awaitから値を返す最良の方法と見なされていますか?
edwardsmarkf 2018

3
@edwardsmarkf個人的には、そのような最善の方法はないと思います。then / catch、async / await、およびコードの非同期部分のジェネレーターでpromiseを使用します。それは主に使用状況に依存します。
アニッシュK.18年

96

非同期関数から値を返す別の方法は、非同期関数の結果を格納するオブジェクトを渡すことです。

以下は同じ例です:

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オブジェクトを使用して、非同期操作中に値を格納しています。これにより、非同期ジョブの後でも結果を利用できます。

私はこのアプローチをよく利用しています。連続するモジュールを介して結果を配線し直す場合に、このアプローチがどの程度うまく機能するか知りたいです。


9
ここでオブジェクトを使用することについて特別なことは何もありません。彼に直接応答を割り当てた場合も同様に機能しますresult。これは、非同期関数が完了したに変数を読み取っているので機能します。
Felix Kling

85

プロミスとコールバックは多くの状況で正常に機能しますが、次のようなものを表現するのは難しいです。

if (!name) {
  name = async1();
}
async2(name);

あなたは最終的に行きますasync1nameが未定義かどうかを確認し、それに応じてコールバックを呼び出します。

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

小さな例では問題ありませんが、同様のケースが多く、エラー処理が含まれていると、煩わしくなります。

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
}

ここでプロジェクトをチェックアウトできます


1
@recurf-それは私のプロジェクトではありません。あなたは彼らの課題追跡を使用してみることができます。
rohithpr 2017年

1
これはジェネレータ関数に似ていますか?developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... *
Emanegux

1
これはまだ関連がありますか?
Aluan Haddad

async-awaitnodeの最新バージョンのいくつかを使用している場合に利用できます。誰かが古いバージョンで立ち往生している場合、この方法を使用できます。
rohithpr 2018年

83

私が書いた次の例は、

  • 非同期HTTP呼び出しを処理します。
  • 各API呼び出しからの応答を待ちます。
  • Promiseパターンを使用します。
  • 複数のHTTP呼び出しに参加するには、Promise.allパターンを使用します。

この作業例は自己完結型です。これは、ウィンドウXMLHttpRequestオブジェクトを使用して呼び出しを行う簡単なリクエストオブジェクトを定義します。一連のPromiseが完了するのを待つ単純な関数を定義します。

環境。この例では、特定のクエリ文字列セットのオブジェクトを検索するために、Spotify Web APIエンドポイントにplaylistクエリを実行しています。

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

各アイテムについて、新しいPromiseはブロックを起動しExecutionBlock、結果を解析し、S​​potify userオブジェクトのリストである結果配列に基づいて新しいPromiseのセットをスケジュールし、ExecutionProfileBlock非同期で新しいHTTP呼び出しを実行します。

次に、ネストされたPromise構造を確認できます。これにより、複数の完全に非同期のネストされたHTTP呼び出しを生成し、を介して呼び出しの各サブセットからの結果を結合できますPromise.all

最近のSpotify searchAPIでは、リクエストヘッダーでアクセストークンを指定する必要があります。

-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" />

このソリューションについては、ここで詳しく説明しました


80

簡単に言え次のようなコールバックを実装する必要があります。

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});

78

2017年の回答:現在のすべてのブラウザーとノードで、思いどおりのことができるようになりました

これは非常に簡単です:

  • 約束を返す
  • 「await」を使用します。これにより、JavaScriptに値が解決されるという約束(HTTP応答など)を待機するよう指示します。
  • 親関数に「async」キーワードを追加します

ここにあなたのコードの作業バージョンがあります:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

awaitは現在のすべてのブラウザーとノード8でサポートされています


7
残念ながら、これはpromiseを返す関数でのみ機能します。たとえば、コールバックを使用するNode.js APIでは機能しません。また、誰もが「現在のブラウザ」を使用しているわけではないため、Babelなしで使用することはお勧めしません。
のMichałPerłakowski

2
@MichałPerłakowskiノード8にはnodejs.org/api/util.html#util_util_promisify_originalが含まれており、node.js APIがpromiseを返すようにするために使用できます。最新でないブラウザをサポートする時間とお金があるかどうかは、明らかに状況に依存します。
mikemaccana 2017年

IE 11は2018年も現在のブラウザーですが、残念ながらサポートされていませんawait/async
Juan Mendes

IE11は現在のブラウザではありません。5年前にリリースされ、caniuseによると、世界市場のシェアは2.5%です。誰かが予算を倍にして現在のすべての技術を無視しない限り、ほとんどの人にとって時間の価値はありません。
mikemaccana 2018年

76

Jsはシングルスレッドです。

ブラウザは3つの部分に分けることができます:

1)イベントループ

2)Web API

3)イベントキュー

イベントループは永久に実行されます。つまり、無限ループのようなものです。イベントキューは、すべての関数がイベントにプッシュされる場所です(例:クリック)。これは、キューから1つずつ実行され、この関数を実行するイベントループに入れられます。つまり、最初の関数が実行された後、次の関数が実行されます。これは、キュー内の関数がイベントループで実行される前の関数まで、1つの関数の実行が開始されないことを意味します。

2つの関数をキューにプッシュしたとしましょう.1つはサーバーからデータを取得するためのもので、もう1つはそのデータを利用します.serverRequest()関数をキューに最初にプッシュしてから、utiliseData()関数をプッシュします。serverRequest関数はイベントループに入り、サーバーからデータを取得するのにかかる時間がわからないため、サーバーへの呼び出しを行います。このプロセスには時間がかかることが予想されるため、イベントループをビジー状態にしてページをハングさせます。 APIが役割を果たし、この関数をイベントループから取得し、イベントループを解放するサーバーを処理して、キューから次の関数を実行できるようにします。キュー内の次の関数は、ループに入るutiliseData()ですが、使用可能なデータがないため、無駄になり、次の関数の実行がキューの最後まで続きます(これは非同期呼び出しと呼ばれます。つまり、データを取得するまで他のことを実行できます)。

serverRequest()関数のコードにreturnステートメントがあったとしましょう。サーバーからデータを取得すると、Web APIはそれをキューの最後のキューにプッシュします。キューの最後にプッシュされると、データを利用するためのキューに残っている関数がないため、データを利用できません。したがって、非同期呼び出しから何かを返すことはできません。

したがって、これに対する解決策は、コールバックまたは約束です。

ここで答えの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");
}

Javscript.infoコールバック


68

このカスタムライブラリ(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)
});

67

別の解決策は、シーケンシャルエグゼキューターnsynjsを介してコードを実行することです。

根本的な機能が約束されている場合

nsynjsはすべてのpromiseを順番に評価し、promiseの結果を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


2
これは面白い。非同期呼び出しを他の言語で行う方法と同じようにコーディングできるのが気に入っています。しかし、技術的には本当のJavaScriptではありませんか?
Jモリス

41

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は配列にラップされ、それらを読み取るときに分解されるため、パターンは複数の引数を持つコールバックに対処できます。たとえば、ノードfsの場合

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

39

以下は、非同期リクエストを処理するためのいくつかのアプローチです。

  1. Browser Promiseオブジェクト
  2. Q -JavaScriptのpromiseライブラリ
  3. A + Promises.js
  4. jQuery据え置き
  5. XMLHttpRequest API
  6. コールバックの概念の使用-最初の回答の実装として

例:複数のリクエストで機能するjQuery遅延実装

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();


38

私たちは、「時間」と呼ばれる次元に沿って進行しているように見える宇宙にいます。私たちは実際には何時なのかはわかりませんが、「過去」、「現在」、「未来」、「前」、「後」のように、それを推論して話すための抽象化と語彙を開発しました。

私たちが構築するコンピューターシステムは、ますます多くの時間を重要な側面として持っています。特定のことが将来起こるように設定されています。次に、これらの最初のことが最終的に発生した後に、他のことが発生する必要があります。これが「非同期性」と呼ばれる基本概念です。ますますネットワーク化された世界では、非同期の最も一般的なケースは、リモートシステムが何らかの要求に応答するのを待っていることです。

例を考えてみましょう。あなたは牛乳屋に電話して牛乳を注文します。それが来るとき、あなたはそれをあなたのコーヒーに入れたいです。まだここにないので、今あなたはコーヒーにミルクを入れることができません。あなたはそれをあなたのコーヒーに入れる前にそれが来るのを待たなければなりません。つまり、以下は機能しません。

var milk = order_milk();
put_in_coffee(milk);

JSは、それが必要であることを知る方法がないので待つためにorder_milk、それが実行される前に、最後までをput_in_coffee。言い換えれば、それorder_milk非同期であることはわかりません- 将来のある時点までは、牛乳にはなりません。JSおよび他の宣言型言語は、待機することなく1つのステートメントを次々に実行します。

この問題に対する古典的な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には、という名前の単一のメソッドがあり、thenPromiseが表す結果が実現されたときに実行するアクションを渡します。

牛乳とコーヒーの場合はorder_milk、牛乳の到着を約束してから、次のようにアクションput_in_coffeeとして指定thenします。

order_milk() . then(put_in_coffee)

これの1つの利点は、これらをつなぎ合わせて将来の発生のシーケンスを作成できることです(「連鎖」)。

order_milk() . then(put_in_coffee) . then(drink_coffee)

あなたの特定の問題に約束を適用しましょう。リクエストロジックを関数内にラップし、promiseを返します。

function get_data() {
  return $.ajax('/foo.json');
}

実際、私たちが行ったのは、returnへの呼び出しにaが追加されただけ$.ajaxです。これが機能するのは、jQueryが$.ajaxすでに一種のpromiseのようなものを返しているためです。(実際には、詳細に触れずに、この呼び出しをラップして、実際のプロミスを返すか、それとは別の方法を使用する$.ajaxことをお勧めします。ここで、ファイルをロードして、終了するまで待ち、それから何かをする、私たちは単に言うことができます

get_data() . then(do_something)

例えば、

get_data() . 
  then(function(data) { console.log(data); });

thenpromiseを使用すると、多くの関数がに渡されるため、ES6スタイルのよりコンパクトな矢印関数を使用すると便利です。

get_data() . 
  then(data => console.log(data));

asyncキーワード

しかし、同期の場合は一方向で、非同期の場合はまったく異なる方法でコードを記述する必要があることについて、漠然と不満な点がまだあります。同期については、

a();
b();

しかし、a非同期の場合は、約束を付けて書く必要があります

a() . then(b);

上記では、「JSは、最初の呼び出しが完了するのを待ってから2番目の呼び出しを実行する必要があることを知る方法がありません」と述べました。JSにそれを伝える方法があったらいいのではないでしょうか?await「async」関数と呼ばれる特別なタイプの関数内で使用されるキーワードがあることがわかります。この機能は、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);
}

37

短い答え:あなたの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ます。


14
これは機能しますが、グローバル変数に割り当てるよりも実際には優れていません。
Felix Kling 2015

36

成功の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();

29

問題は:

非同期呼び出しから応答を返すにはどうすればよいですか?

これは次のように解釈できます。

非同期コードを同期的に見えるようにする方法は?

解決策は、コールバックを回避し、Promiseasync / 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")

29

Promiseの使用

この質問に対する最も完璧な答えは、を使用すること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
});

ちょっと待って...!

プロミスの使用に問題があります!

なぜ独自のカスタムPromiseを使用する必要があるのですか?

古いブラウザにエラーがあることがわかるまで、私はしばらくこのソリューションを使用していました:

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;
    }();
}

28

もちろん、同期リクエスト、プロミスのような多くのアプローチがありますが、私の経験から、コールバックアプローチを使用する必要があると思います。Javascriptの非同期動作は自然です。したがって、コードスニペットは少し異なるように書き換えることができます。

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}

5
コールバックやJavaScriptについて本質的に非同期なものはありません。
Aluan Haddad

19

JSがコールバックと非同期性を処理する方法を理解するための鍵となる2つの概念があります。(それは一言ですか?)

イベントループと同時実行モデル

注意する必要のある3つの点があります。待ち行列; イベントループとスタック

広範に単純化した言葉で言えば、イベントループはプロジェクトマネージャーのようなものであり、実行したい関数を常にリッスンし、キューとスタックの間で通信します。

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

何かを実行するメッセージを受信すると、キューに追加します。キューは、(AJAXリクエストのように)実行を待機しているもののリストです。次のように想像してみてください。

 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は、メッセージがキューに追加されるのを待つ間、キュー内の次のアイテムの実行を続けます。

これが、非同期機能でコールバックと呼ばれるものを使用する理由です。それは文字通り約束のようなものです。ある時点で何かを返すこと約束するように、 jQueryはdeffered.done deffered.faildeffered.always(および)と呼ばれる特定のコールバックを使用します。あなたはここでそれらすべてを見ることができます

したがって、必要なのは、渡されたデータを使用して、ある時点で実行されることが約束されている関数を渡すことです。

コールバックはすぐには実行されませんが、後で実行されるのではなく、関数への参照を渡すことが重要です。そう

function foo(bla) {
  console.log(bla)
}

ほとんどの場合(常にではありませんが)合格しfooないでしょうfoo()

うまくいけば、それはいくつかの理にかなっています。このように混乱しそうなことに遭遇した場合、少なくともドキュメントを完全に読んで理解することを強くお勧めします。それはあなたをはるかに優れた開発者にします。


18

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)

})

その2番目の機能は再利用可能ですか?
Zum Dummi

oncolse、logが呼び出された場合、結果をどのように使用しますか?その時点ですべてがコンソールに行きませんか?
Ken Ingram
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.