javascript async mdn




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

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

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

→問題をすでに理解している場合は、以下の解決策にスキップしてください。

問題

AjaxAasynchronous表しasynchronous 。 つまり、リクエストの送信(または、むしろレスポンスの受信)は、通常の実行フローから取り出されます。 あなたの例では、 $.ajaxは直ちに戻り、次の文return result; successコールバックが呼び出されたときに渡された関数が呼び出される前に実行されます。

うまくいけば、同期フローと非同期フローの違いをより明確にする類推があります:

同期

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

通常のコードを含む関数呼び出しを行うと、同じことが起こります。

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

var item = findItem();

// Do something with item
doSomethingElse();

findItemは実行に時間がかかるかもしれませんが、 var item = findItem(); 関数が結果を返すまで待たなければなりません

非同期

あなたは同じ理由であなたの友人に再度電話します。 しかし今度は、あなたが急いでいると彼に伝えると、彼はあなたの携帯電話であなたに電話してください。 電話を切って、家を出て、あなたがやろうとしたことを何でもしてください。 あなたの友人があなたに電話をかけたら、彼はあなたに与えた情報を扱っています。

それはまさにAjaxリクエストを行うときに起きていることです。

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

応答を待つ代わりに、実行はただちに継続され、Ajax呼び出し後のステートメントが実行されます。 応答を最終的に取得するには、応答を受け取った後に呼び出す関数、 コールバックcall backcall back ?)を提供します。 呼び出し後に来るステートメントは、コールバックが呼び出される前に実行されます。

ソリューション

JavaScriptの非同期性を取り入れましょう! 特定の非同期操作では同期の相手が提供されますが(「Ajax」も同様です)、通常はブラウザのコンテキストで使用することをお勧めします。

なぜそれは悪いですか?

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

これはすべてユーザーの利便性が悪いことです。 ユーザーは、すべてが正常に動作しているかどうかを確認することはできません。 さらに、接続が遅いユーザーにとっては、その影響はさらに深刻になります。

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

  • async/await約束します(ES2017 +、古いブラウザでは、蒸散器や再生器を使用しています)
  • コールバック (ノードで一般的)
  • then()約束します(ES2015 +、多くの約束ライブラリの1つを使用する場合は古いブラウザで利用可能)

3つは現在のブラウザで利用可能であり、ノード7+はすべて利用可能です。

ES2017 +: async/await待機を約束

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

async/awaitは約束の上に構築されます: async関数は常に約束を返します。 約束を「展開」し、その約束が却下された場合は約束を破棄します。

重要: async関数内でのみawaitを使用できます。 つまり、最上位レベルでは、依然として約束に直接従わなければなりません。

async/awaitawait詳細については、MDNで読むことができます。

上記の遅延の上に構築された例を次に示します。

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

現在のbrowsernodeバージョンはasync/awaitサポートし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を呼び出すときに渡す関数を参照し、成功した関数に渡します。 つまり、Ajaxリクエストが成功すると、 $.ajaxはコールcallbackを呼び出し、その応答をコールcallbackに渡します(これは、コールバックを定義した方法なのでresultで参照できます)。

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

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

コールバックを使用してコードを書くほうが簡単かもしれません。 結局のところ、ブラウザのJavaScriptは、イベント駆動型(DOMイベント)が多いです。 Ajaxレスポンスを受信することは他のイベントではありません。
サードパーティ製のコードで作業する必要があるときに問題が発生する可能性がありますが、ほとんどの問題はアプリケーションの流れを考えて解決できます。

ES2015 +: then()約束します。

then()は、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の遅延オブジェクト

遅延オブジェクトは、(Promise APIが標準化される前の)jQueryの約束のカスタム実装です。 彼らは約束のように振る舞いますが、わずかに異なるAPIを公開します。

jQueryのすべてのAjaxメソッドは、すでに関数から返すことができる "遅延オブジェクト"(実際には遅延オブジェクトの約束)を返します。

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

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

サイドノート:Promise gotchas

約束と繰延オブジェクトは将来の価値のための単なる容器であり、価値そのものではありません。 たとえば、次のものがあるとします。

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として扱い、ユーザーがログインしたかのように処理することを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.openます。

jQuery

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

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

$.get$.getJSONなどの他のjQuery Ajaxメソッドを使用する場合は、 $.getJSONに変更する必要があります( $.ajaxのみ設定パラメータを渡すことができるため)。

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


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

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

1)イベントループ

2)Web API

3)イベントキュー

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

キューからサーバへデータを取得するためのキューと、そのデータを利用するキューの2つの関数をプッシュしたとしましょう。最初にqueueで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");
}

非同期呼び出しを行うためのECMA(2016/17)の新しいメソッドについては、こちらをhttps://.com/a/14220323/7579856 @Felix Kling Answer)https://.com/a/14220323/7579856


コードで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または約束の下で別の答えを追加しました )

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

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

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"が表示されます。 (Fiddle)

可能な解決策

これを解決するには基本的に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();
}

(fiddle)

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

あなたがまだこれを理解するのに苦労しているなら、MDNで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並行処理モデルは、 非同期反応的です。

約束を返すメソッドを呼び出すたびに、 thenハンドラは常に非同期で実行されます。つまり、 thenハンドラの下のコードが.thenハンドラにない場合です。

つまり、定義したハンドラがまだ実行されていないdata返すときです。 これは、戻り値が正しい値に設定されていないことを意味します。

この問題の簡単な類推は次のとおりです。

    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 = 5の値はundefinedです。 それは2番目に実行される可能性が高いですが、その時までに返される値とは無関係です。

操作はまだ(AJAX、サーバーコール、IO、タイマー)は発生していないので、リクエストがあなたのコードにその値が何であるかを伝えるチャンスがある前に、値を返すことになります。

この問題の解決策の1つは、計算が完了したときに何をすべきかをプログラムに伝えることです。 約束は、積極的に時間的(時間に敏感)であることによってこれを可能にする。

約束のクイックリキャップ

プロミスは時間の経過と共に価値があります 。 プロミスには状態があり、彼らは価値がないまま保留状態になり、決済することができます:

  • 計算が正常に完了したこと意味します。
  • これは計算が失敗したことを意味します。

約束は、 一度状態を変えることができます。その後、常に同じ状態にとどまります。 ハンドラをアタッチして、値を抽出してエラーを処理することを約束することができます。 ハンドラはコールのchainingを許可しchaining 。 約束はそれらを返すAPIを使用して作成されます 。 たとえば、より現代的なAJAXの置換fetchやjQueryの$.getリターンの約束などです。

私たちが約束を呼び、そこから何かを返すとき、私たちは処理された価値について約束を得ます 。 私たちがもう一度約束を返せば、驚くべきことを得るでしょうが、馬をつかみましょう。

約束

約束で上記の問題を解決する方法を見てみましょう。 まず、 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を約束を使用するように変換した後で、それを使用して数えることができます:

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

基本的には、並行性モデルのためにできないを返す代わりに、 ラッパーで返します。 それはあなたがその時に開くことができるボックスのようなものです。

これを適用する

これは元の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では、途中で復帰できる機能である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,....。これは単独では面白いが、多くの可能性のために部屋を開くが、特に興味深いケースがある。

生成するシーケンスが数値ではなく一連のアクションである場合、アクションが生成されるたびに関数を一時停止し、関数を再開する前に関数を待機させることができます。だから、一連の数字の代わりに、私たちは将来の一連の値、すなわち約束を必要とします。

これはやや難解だが非常に強力なトリックで、非同期コードを同期的に書くことができます。あなたのためにこれを行ういくつかの "ランナー"がありますが、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
});

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

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約束することができます。これは、上記のES6の提案では、asyncawaitキーワードを追加することによって、 "砂糖"(より良い構文)に過ぎません。上記の例を作成する:

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
}

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


非同期要求を処理するには、いくつかのアプローチがあります。

  1. then()
  2. Q - JavaScript用の約束ライブラリ
  3. + 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();


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. これは新しいonloadを使用します(readystate && statusをチェックする必要はありません)
  4. XMLHttpRequest 1を厄介なものにすることを覚えていない状況がいくつかあります。

このAjax呼び出しの応答を取得するには、2つの方法があります(3つはXMLHttpRequest var名を使用します)。

最も簡単な:

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

ご覧のとおり、私は同期を実装していない...それは悪いことだ。

それは簡単なことではないのですか?

コメントで述べたように、エラー&& synchronousの使用は、解答のポイントを完全に破ります。 適切な方法でAjaxを使用するのは簡単な方法ですか?

エラーハンドラ

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

上記のスクリプトでは、静的に定義されたエラーハンドラを持っているので、関数を危険にさらすことはありません。 エラーハンドラは他の関数にも使用できます。

しかし、実際にエラーを出すには、間違ったURLを書くことが唯一の方法です。その場合、すべてのブラウザでエラーが発生します。

エラーハンドラーは、カスタムヘッダーを設定し、responseTypeをBLOB配列バッファーなどに設定すると便利かもしれません。

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

formdataとして 'fdggdgilfdghfldj'を渡しても、エラーは発生しません。

最初のケースでは、このエラーはdisplayAjax()下のdisplayAjax()中にMethod not Allowedます。

後者の場合、それは単に機能します。 適切な投稿データを渡したかどうかをサーバー側で確認する必要があります。

クロスドメインが許可されないと、自動的にエラーがスローされます。

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

エラーに設定されている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を使用しています。最初の関数の1つは、すべてのHTMLページに統合されています。この答えの最初のAjax関数は、GETのみです...

しかし、あなたはXMLHttpRequest 2でたくさんのことをすることができます:

私は、ダウンロードマネージャ(resume、filereader、filesystemの両側に範囲を使用)、キャンバスを使用したさまざまなイメージリサイザコンバーター、web64データベースをbase64イメージに埋め込むなどの作業を行いました...しかし、これらの場合は、 ...時にはBLOB、配列バッファが必要な場合、ヘッダーを設定し、mimetypeをオーバーライドすることができます。さらに多くのことがあります...

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


木々を見る前にまず森を見てみましょう。

ここでは詳細な情報がたくさんありますが、私はそれらのどれも繰り返しません。JavaScriptでのプログラミングの鍵は、最初に全体的な実行の正しい精神モデルを持つことです。

  1. エントリポイントは、イベントの結果として実行されます。たとえば、コード付きのスクリプトタグがブラウザにロードされます。(したがって、dom要素を最初に構築する必要がある場合は、コードを実行する準備が整っているかどうか心配する必要があります)
  2. あなたのコードは、XHR要求、設定されたタイムアウト、DOMイベントハンドラなどのコールバックを実行することなく、完了まで実行されます(非同期呼び出しの多くは実行されます)。実行を待っているコールバックは、彼らのターンは、発砲した他のイベントの後に実行され、すべての実行が終了しました。
  3. 一度呼び出されると、XHR要求への個々のコールバック、set timeoutまたはdomが完了まで実行されます。

良いことは、この点をよく理解すれば、競争条件を心配する必要がないということです。あなたは、コードを本質的に異なる離散イベントへの応答としてどのように整理したいのか、それらを一緒に論理的なシーケンスにどのようにスレッド化したいのか、まず第一にすべきです。その目的のための約束として、より高いレベルの新しいasync / awaitをツールとして使用することも、独自のロールを使用することもできます。

しかし、実際の問題領域に慣れていない限り、問題を解決するための戦術的ツールは使用しないでください。これらの依存関係のマップを描画して、いつ実行する必要があるかを把握します。これらすべてのコールバックに対するアドホックなアプローチを試みるだけでは、あなたにうまく対応できません。


簡単な答え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ます。


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)

})

このカスタムライブラリ(プロミスを使用して記述)を使用して、リモート呼び出しを行うことができます。

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

私が書いた次の例は、

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

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

コンテキスト。この例では、指定された一連のクエリ文字列のオブジェクトを検索するために、Spotify Web APIエンドポイントにplaylistクエリを実行しています。

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

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

次に、複数の完全非同期のネストされたHTTP呼び出しを生成し、呼び出しの各サブセットの結果を結合することができる、ネストされたPromise構造を見ることができます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" />

私は広範囲にこの解決策について議論しhere


約束事とコールバックは多くの状況でうまくいくが、後ろの痛みは次のようなものである。

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

あなたは終わるだろうasync1。if nameが未定義であるかどうかをチェックし、それに応じてコールバックを呼び出します。

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
}

hereプロジェクトをチェックアウトすることができhere


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の機能について質問しましょう。プロミスとは、実際には何を意味するのかということです。これは、将来その機能がどのような出力を提供することを保証することを意味します。上記の小さな問題で動いてみましょう:

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(あなたが返す予定)値に約束を使用することができます。したがって、要するに、非同期関数ではなく戻り値を解決します。

UPDATE(非同期/待機の約束)

then/catch約束をするために使う以外にも、もう一つのアプローチがあります。アイデアがすることです非同期関数を認識して、約束を待つ解決するために、コードの次の行に移動する前に。それはまだ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;
     }
  }

あなたは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);
    }
});

送信ハンドラで何かを返すことは何もしません。その代わりに、データを手渡しするか、成功関数の中で直接実行しなければなりません。


これは、多くの新しいJavaScriptフレームワークで使用されている2つの方法のデータバインディングが、あなたにとって大きく役立つ場所の1つです...

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

しかし、あなたはこの質問で尋ねたように、例えば純粋なjavascriptjQueryでどのようにすることができますか?

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

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

詳細については、この非同期の処理を行う新しい方法である約束観測可能性について学びます。


もちろん、同期リクエスト、約束のような多くのアプローチがありますが、私の経験からはコールバックのアプローチを使うべきだと思います。Javascriptの非同期動作には当然です。したがって、コードスニペットを少し書き直すことができます:

function foo() {
    var result;

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

    return result;
}

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

別の解決策は、シーケンシャル実行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-awareラッパーにコールバック関数をラップします(バージョンを約束している場合は、このテストをスキップできます)。

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. nnsynjsを介して同期的に関数を実行します。

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjsは、すべての演算子と式を段階的に評価し、遅い関数の結果が準備できていない場合に実行を中断します。

その他の例はこちら:https://github.com/amaksr/nsynjs/tree/master/examples : https://github.com/amaksr/nsynjs/tree/master/examples


成功の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。言い換えれば、それ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);
}

約束を入力

これは、ある種の将来または非同期の結果を表す特定のタイプの価値である「約束」という概念の動機付けでした。すでに起こっていること、あるいは将来起こること、あるいはまったく起こりえないことを表すことができます。約束は、約束thenされた結果が実現されたときに実行されるアクションを渡す、単一のメソッドを持っています。

私たちのミルクとコーヒーの場合、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');
}

実際には、私たちが行ったreturnことは、に呼び出しに追加され$.ajaxます。これは、jQueryが$.ajaxすでにある種の約束のようなものを返すために機能します。(実際には、詳細を知ることなく、実際の約束を返すようにこの呼び出しをラップする方が良いでしょう。あるいは、そうする代替手段を使用する$.ajaxこともできます)。ファイルをロードして終了するまで待ちます何かをする、我々は単に言うことができます

get_data() . then(do_something)

例えば、

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

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

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

asyncキーワード

しかし、まだ同期していれば一方的なコードを書かなければならず、非同期の場合には全く別の方法でコードを書かなければならないということには、あまりにも不満があります。同期の場合は、

a();
b();

しかし、もしa非同期であれば、私たちは約束して書く必要があります

a() . then(b);

上の例では、「JSは、最初の呼び出しが完了するのを待ってから2番目の呼び出しを実行するまで待つ必要があることを知る方法がない」と述べました。JSにそれを伝える方法あればいいのではないでしょうか?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);
}

私は恐ろしい、手描きの漫画で答えるだろう。2番目のイメージは、コード例の理由resultですundefined





ecmascript-2017