非同期呼び出しからレスポンスを返すには

javascript jquery ajax asynchronous


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`.




Answer 1 Felix Kling


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

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

問題点

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

ここでは、うまくいけば同期フローと非同期フローの違いがより明確になるような例えがあります。

Synchronous

あなたが友人に電話をかけて、あなたのために何かを調べてくれるように頼んだと想像してみてください。それはしばらくかかるかもしれませんが、あなたの友人があなたが必要としていた答えを与えるまで、あなたは電話で待って、スペースに凝視します。

普通の」コードを含む関数呼び出しをしても同じことが起きています。

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

findItem の実行には時間がかかる場合がありますが、 var item = findItem(); 後に続くコードはすべて、 関数が結果を返すまで待つ必要があります。

Asynchronous

同じ理由で友達に再度電話をかけます。 しかし、今度はあなたが急いでいるので、彼はあなたの携帯電話にかけ直すべきだと彼に言います 。 あなたは電話を切り、家を出て、あなたが計画していたことを何でもします。 あなたの友人があなたに電話をかけたら、あなたは彼があなたに与えた情報を扱います。

まさにAjaxリクエストをした時に起こっていることです。

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

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


Solution(s)

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

なぜそれが悪いことなのか、あなたは尋ねますか?

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

このすべては、本当に悪いユーザー体験です。ユーザーは、すべてが正常に動作しているかどうかを見分けることができません。さらに、接続速度が遅いユーザーには影響が悪化します。

以下では、すべてがお互いの上に構築されている3つの異なるソリューションを見ていきます。

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

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


ES2017 +: async/await 待機での約束

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

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

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

あなたは async についてもっと読むことができ、MDNで 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'

becomes

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 、 resultで参照できます)。

また、コールバックに渡す前にレスポンスを処理することもできます。

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

コールバックを使ったコードを書くのは意外と簡単です。結局のところ、ブラウザの JavaScript はイベント駆動型 (DOM イベント)です。Ajax のレスポンスを受信することはイベント以外の何物でもありません。
サードパーティのコードを使用しなければならない場合には困難が生じる可能性がありますが、ほとんどの問題はアプリケーションの流れを考えるだけで解決することができます。


ES2015 +: then()で約束

Promise 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 コールに適用すると、このようなプロミスを使うことができます。

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

プロミスが提供するすべての利点を説明することは、この回答の範囲を超えていますが、新しいコードを書く場合は、真剣にプロミスを検討する必要があります。プロミスはコードの大きな抽象化と分離を提供してくれます。

プロミスに関する詳細情報: HTML5のロック-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 オブジェクトを直接使用する場合は、 .open 3番目の引数として false を渡します。

jQuery

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つあります)。




Answer 2 Benjamin Gruenbaum


コードで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はAJAXのためにjQueryを使用している人のための回答を書いてくれましたが、そうでない人のために代替案を提供することにしました。

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


あなたが直面していること

他の解答から「問題の解説」を簡潔にまとめたものですので、これを読んでわからなくなった方はそちらを読んでみてください。

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

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

ここで簡単な例え話をします。

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

(Fiddle)

a=5 の部分はまだ実行されていないため、返される値は undefined です。 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 が完了したときにどのように反応するかをコードに伝えます。

So:

var result = foo();
// code that depends on `result` goes here

Becomes:

foo(function(result) {
    // code that depends on `result`
});

ここでは匿名の関数を渡しましたが、既存の関数への参照を渡すのと同じように簡単に渡すことができ、次のようになります。

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

この種のコールバック設計の詳細については、Felix の回答を参照してください。

では、それに応じて動作するように foo 自体を定義してみましょう。

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(fiddle)

AJAXが正常に完了したときに実行されるアクションを受け入れるようにfoo関数を作ったので、レスポンスのステータスが200でないかどうかをチェックして、それに応じて行動することでこれをさらに拡張することができます(failハンドラなどを作る)。実質的に問題を解決しています。

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




Answer 3 cocco


XMLHttpRequest 2 (まず、 Benjamin GruenbaumFelix 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. 他にも、XMLHttpRequest1がめんどくさいと思うような状況がいくつかありますが、私は覚えていません。

このAjaxコールのレスポンスを取得するには2つの方法があります(XMLHttpRequest var名を使った3つの方法)。

一番シンプルなのは

this.response

または、何らかの理由でコールバックをクラスに bind() した場合:

e.target.response

Example:

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 変数名を使用すると、もう1つの大きなエラーになります。それは、onload / oreadystatechangeクロージャー内でコールバックを実行する必要があるためです。


ポストや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を書き込むことです。

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

メソッドに 'POSTAPAPAP' を渡してもエラーは出ません。

formdata に 'fdggdgilfdghfldj' を渡してもエラーにはなりません。

最初の場合、エラーは displayAjax() 下のdisplayAjax()内の 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ループで...でもマジで?)

もう一つのポイントは...API を使って作業する場合、あるいは自分のリストのファイルを使って作業する場合など、リクエストごとに異なる関数を使うことになります...

常に同じXMLJSONなどを読み込んでいるページがある場合に限り、1つの関数だけで済みます。その場合は、Ajax関数を少し修正して、bをあなたの特別な関数に置き換えてください。


上記の機能は基本的な使い方です。

機能をEXTENDしたい場合は

はい、できます。

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

しかし、XMLHttpRequest 2を使えば色々なことができます。

私はダウンロードマネージャ(レジューム、ファイルリーダ、ファイルシステムで両側に範囲を使用)、canvasを使った様々なイメージリサイザコンバータ、base64イメージを使ったWeb SQLデータベースの生成などを作りました...。しかし、これらのケースでは、その目的のためだけに関数を作成する必要があります...時にはブロブや配列バッファが必要になることもありますし、ヘッダを設定したり、mimetypeをオーバーライドしたり、他にもたくさんのことがあります...

しかし、ここで問題になるのはAjaxのレスポンスをどうやって返すか・・・。(簡単な方法を追加してみました)




Answer 4 Benjamin Gruenbaum


プロミスを利用しているなら、この答えはあなたのためのものです。

これは、AngularJS、jQuery(with deferred)、ネイティブXHRの置換(fetch)、EmberJS、BackboneJSのsave、またはプロミスを返す任意のノードライブラリを意味します。

あなたのコードは以下のようなものになるはずです。

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 ハンドラー内にない、その下のコードの後にあります。

これは、 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 = 5 パートはまだ実行されていないため、 dataの値は undefined です。 おそらく1秒で実行されますが、その時点では戻り値とは無関係です。

操作はまだ起きていないので(AJAX、サーバーコール、IO、タイマー)、リクエストがその値が何であるかをコードに伝える機会を得る前に値を返しています。

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

約束事の簡単な復習

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

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

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

.then をプロミスで呼び出し、そこから何かを返す、処理された値のプロミスが得られます 。 別の約束を返せば素晴らしいものを手に入れられますが、馬を抱きましょう。

約束事で

上記の問題を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アンラップできる値のラッパーを返します。 それはあなたがそれで開くことができる箱のようなものです。

これを適用すると

これは、あなたの元の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`
})

ということで、これも同じように動作します。すでに非同期呼び出しをしている場合は値を返すことができないことがわかりましたが、プロミスを使ってチェーン化して処理を行うことはできます。これで、非同期呼び出しからレスポンスを返す方法がわかりました。

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つは短いコード行ですが、この回答の範囲を超えています。 ここではBluebirdの Promise.coroutine を使用しますが、 coQ.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();

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
}

それはまだ同じように約束を返します :)




Answer 5 Nic


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

submit ハンドラで何かを返しても何もしません。その代わりに、データを渡すか、あるいはサクセス関数の中で直接データを処理する必要があります。