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

5743
Felix Kling 2013-01-09 07:06.

foo非同期リクエストを行う機能があります。からの応答/結果を返すにはどうすればよいfooですか?

コールバックから値を返し、その結果を関数内のローカル変数に割り当てて返すことを試みましたが、実際には応答を返しません(すべてが返されるundefinedか、変数の初期値が何であれresult) 。

jQueryのajax関数を使用した例:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result; // It always returns `undefined`
}

node.jsの使用例:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

thenpromiseのブロックを使用した例:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

30 answers

5905
Felix Kling 2013-01-09 07:06.

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

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

問題

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

応答を待つ代わりに、実行はすぐに続行され、Ajax呼び出しの後のステートメントが実行されます。最終的に応答を取得するには、応答を受信したときに呼び出される関数、コールバックを提供します(何かに注意してください?コールバック?)。その呼び出しの後に来るステートメントは、コールバックが呼び出される前に実行されます。


解決策

JavaScriptの非同期性を取り入れましょう!特定の非同期操作は同期の対応物を提供しますが(「Ajax」もそうです)、特にブラウザーのコンテキストでは、それらを使用することは一般的に推奨されていません。

なぜ悪いのですか?

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

これはすべて、本当に悪いユーザーエクスペリエンスです。ユーザーは、すべてが正常に機能しているかどうかを判断できなくなります。さらに、接続速度が遅いユーザーの場合、影響はさらに悪化します。

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

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

3つすべてが現在のブラウザ、およびノー​​ド7以降で使用できます。


ES2017 +:との約束 async/await

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

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

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

MDNについてasync、およびawaitMDNについて詳しく読むことができます。

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

// 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に変換することにより、古い環境をサポートすることもできます。


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

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

質問の例では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)の新機能ですが、それは良い持っているブラウザのサポートをすでに。標準のPromisesAPIを実装し、非同期関数(bluebirdなど)の使用と構成を容易にする追加のメソッドを提供するライブラリも多数あります。

Promiseは、将来の価値のコンテナです。promiseが値を受け取る(解決される)か、キャンセルされる(拒否される)と、この値にアクセスしたいすべての「リスナー」に通知します。

プレーンコールバックに対する利点は、コードを分離でき、作成が簡単になることです。

これが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);
  });
}

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のカスタム実装です(Promise APIが標準化される前)。それらはほぼ約束のように動作しますが、わずかに異なるAPIを公開します。

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

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

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

補足:約束の落とし穴

約束と延期されたオブジェクトは将来の価値の単なるコンテナであり、価値そのものではないことに注意してください。たとえば、次のようなものがあるとします。

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

このコードは、上記の非同期の問題を誤解しています。具体的に$.ajax()は、サーバーの「/ password」ページをチェックしている間はコードをフリーズしません。サーバーにリクエストを送信し、待機している間、サーバーからの応答ではなく、jQuery AjaxDeferredオブジェクトをすぐに返します。つまり、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。このオプションは、jQuery1.8以降非推奨になっていることに注意してください。その後、successコールバックを使用するかresponseTextjqXHRオブジェクトのプロパティにアクセスできます

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

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

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

1096
Benjamin Gruenbaum 2013-05-30 13:30.

コードで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();
}

(フィドル)

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

それでもこれを理解するのに苦労している場合は、MDNのAJAXスタートガイドをお読みください

411
cocco 2013-08-19 22:06.

XMLHttpRequestの2(最初のすべての答えから読みベンジャミンGruenbaumフェリックスクリングを

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


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として渡しても、エラーはスローされません。

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

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

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

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

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

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

したがって、URLを適切にコピーして貼り付けることができれば、エラーチェックは必要ありません。;)

PS:最初のテストとしてx( 'x'、displayAjax)...を書きましたが、完全に応答がありました... ??? そこで、HTMLが配置されているフォルダーを確認したところ、「x.xml」というファイルがありました。したがって、ファイルの拡張子を忘れた場合でも、XMLHttpRequest2はそれを見つけます。私は大爆笑だ


同期ファイルの読み取り

そうしないでください。

しばらくの間ブラウザをブロックしたい場合は、.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のみです...

しかし、XMLHttpRequest2では多くのことができます。

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

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

326
Benjamin Gruenbaum 2015-05-12 16:22.

あなたが約束を使用しているなら、この答えはあなたのためです。

これは、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に対する答えがあります。この回答は、フロントエンドまたはバックエンドでのpromiseの一般的な使用法に関するものです。


中心的な問題

ブラウザと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つの可能な解決策は、計算が完了したときに何をすべきかをプログラムに指示して、リアクティブにコーディングすることです。Promiseは、本質的に時間的(時間に敏感)であることにより、これを積極的に可能にします。

約束の簡単な要約

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

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

プロミスは一度だけ状態を変更できその後は常に同じ状態に永久に留まります。thenハンドラーをpromiseにアタッチして、その値を抽出し、エラーを処理できます。thenハンドラーを使用すると、呼び出しをコールバックだけを約束しませんか?せることができコールバックだけを約束しませんか?。Promiseは既存のコールバックAPIをpromiseに変換するにはどうすればよいですか?て作成され既存のコールバックAPIをpromiseに変換するにはどうすればよいですか?。たとえば、より最新の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。それはあなたが開くことができる箱のようなものです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つあります。

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

このややトリッキーですが非常に強力なトリックにより、非同期コードを同期的に記述できます。これを行う「ランナー」がいくつかあります。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約束できます。これは、asyncandawaitキーワードを追加することによる、上記の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
}

それでも同じ約束を返します:)

256
Nic 2014-05-23 16:05.

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

送信ハンドラーで何かを返しても何も起こりません。代わりに、データを渡すか、成功関数内で直接データを使用して必要な処理を行う必要があります。

242
Hemant Bavle 2014-02-19 08:58.

最も簡単な解決策は、JavaScript関数を作成し、それをAjaxsuccessコールバック用に呼び出すことです。

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);    
}); 
229
Johannes Fahrenkrug 2016-08-12 04:17.

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

165
Maleen Abewardana 2014-08-26 22:11.

Angular1

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

159
T.J. Crowder 2017-05-04 06:59.

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

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

例:

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

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

動作しない理由はdoSomethingAsync、結果を使用しようとしている時点では、からのコールバックがまだ実行されていないためです。

したがって、配列(またはある種のリスト)があり、エントリごとに非同期操作を実行する場合は、2つのオプションがあります。操作を並列(オーバーラップ)または直列(順番に)で実行します。

平行

それらすべてを開始し、予想されるコールバックの数を追跡し、その数のコールバックを取得したときに結果を使用できます。

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

例:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(私たちは廃止しexpectingて使用することもできますresults.length === theArray.lengthが、それtheArrayは通話が未解決の間に変更される可能性に私たちを開いたままにします...)

結果が順不同で到着した場合でも、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);
});

例:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

または、Promise代わりに次のバージョンが返されます。

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

もちろん、doSomethingAsyncエラーが渡された場合rejectは、エラーが発生したときに約束を拒否するために使用します。)

例:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(または、代わりに、doSomethingAsyncpromiseを返すラッパーを作成してから、以下を実行することもできます...)

場合は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);
});

例:

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

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

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

シリーズ

操作を並行させたくないとしましょう。それらを次々に実行したい場合は、次の操作を開始する前に、各操作が完了するのを待つ必要があります。これを実行し、結果を使用してコールバックを呼び出す関数の例を次に示します。

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(私たちは連続して作業を行っているのでresults.push(result)、順序が狂うことはないことがわかっているので、そのまま使用できます。上記では使用できましたがresults[index] = result;、次の例の一部ではインデックスがありません。使用します。)

例:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

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

場合はdoSomethingAsync、あなたに約束を与えるあなたは(おそらくのような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);
});

例:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

ES2017 +構文を(まだ)使用できない場合は、javascript-複数のPromise.allを減らすにはどうすればよいですか?バリエーションを使用できます(これは、結果を次の1つに渡さないため、通常のPromise reduceよりも複雑ですが、代わりに結果を配列にまとめる):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

例:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

...これはES2015 +の矢印関数ではそれほど面倒ではありません:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

例:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

113
Francisco Carmona 2016-06-02 22:31.

この例を見てください:

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

ご覧のとおり解決済みのpromisegetJoke返されます(返されるときに解決されます)。したがって、$ http.getリクエストが完了するまで待ってから、console.log(res.joke)が実行されます(通常の非同期フローとして)。res.data.value

これは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);
  });
})();
112
Alireza 2017-05-24 23:38.

これは、多くの新しい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
});

この非同期処理を行うための新しい方法であるpromiseobservableについての詳細を調べてください。

105
Anish K. 2017-11-01 10:12.

これは、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を要求してみましょう。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、値へのpromiseを使用できます(これを返す予定です)。したがって、要するに、非同期関数では、を返す代わりに値を解決します。

UPDATE(async / awaitでの約束)

then/catch約束を処理するために使用する以外に、もう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);
     }
  }
101
jsbisht 2015-09-03 02:54.

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

これは同じ例です:

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

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

89
rohithpr 2016-01-26 07:43.

約束とコールバックは多くの状況でうまく機能しますが、次のようなことを表現するのは後部の苦痛です。

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

あなたは通過することになりasync1ます; 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
}

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

88
loretoparisi 2016-04-13 12:55.

私が書いた次の例は、

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

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

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

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

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

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

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

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

Spotify WebAPIを使用して名前でユーザーを検索する方法は、このソリューションについて詳しく説明しSpotify WebAPIを使用して名前でユーザーを検索する方法

84
Pablo Matias Gomez 2016-04-23 04:47.

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

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

$.ajax({
    url: "...",
    success: callback
});
82
mikemaccana 2017-06-02 23:51.

2017年の回答:現在のすべてのブラウザーとノードで、必要なことを正確に実行できるようになりました

これは非常に簡単です。

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

コードの動作バージョンは次のとおりです。

(async function(){

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

})()

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

80
Aniket Jha 2018-02-03 20:06.

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

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

1)イベントループ

2)Web API

3)イベントキュー

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

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

serverRequest()関数のコードにreturnステートメントが含まれていると仮定します。サーバーからデータを取得すると、WebAPIはそれをキューの最後のキューにプッシュします。このデータを利用するための関数がキューに残っていないため、キューの最後にプッシュされるため、そのデータを利用できません。したがって、AsyncCallから何かを返すことはできません。

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

ここでの回答の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コールバック

70
Vinoth Rajendran 2016-05-27 03:26.

このカスタムライブラリ(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)
});
70
amaksr 2017-05-27 16:47.

別の解決策は、シーケンシャルエグゼキュータ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

42
James 2018-02-18 05:26.

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またはclosure-compilerを介してコードを実行し、ECMAScript5を生成できます。

...argsパターンが複数の引数を持つコールバックに対処できるように、コールバックは配列にラップされ、それらを読み取るときに非構造化されます。たとえば、ノードfsの場合

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
39
Mohan Dere 2016-08-13 23:36.

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

  1. BrowserPromiseオブジェクト
  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やその他の宣言型言語は、待たずに次々とステートメントを実行します。

この問題に対する従来の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への呼び出しに追加されただけ$.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
David R Tribble 2015-09-24 12:52.

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

36
Mahfuzur Rahman 2017-04-24 22:09.

成功の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();
30
Amir Fo 2018-12-08 04:10.

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の使用に問題があります!

なぜ独自のカスタム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;
    }();
}
29
Pieter Jan Bonestroo 2018-01-14 09:13.

質問は:

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

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

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

解決策は、コールバックを回避し、Promisesasync / 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")
28
Khoa Bui 2017-07-06 10:28.

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
26
SanjiMika 2020-01-20 12:23.

ここですべての応答を読み、私の経験を踏まえて、callback, promise and async/awaitJavaScriptでの非同期プログラミングの詳細を再開したいと思い ます。

1)コールバック:コールバックの基本的な理由は、イベントに応答してコードを実行することです(以下の例を参照)。JavaScriptでは毎回コールバックを使用しています。

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

ただし、以下の例でネストされたコールバックを多数使用する必要がある場合、コードのリファクタリングは非常にひどいものになります。

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2)Promise:構文ES6-Promiseはコールバック地獄の問題を解決します!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code. 
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
}) 

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromiseは、非同期コードのプロセスを表すPromiseインスタンスです。resolve関数は、Promiseインスタンスが終了したことを通知します。その後、promiseインスタンスで.then()(必要に応じて.thenのチェーン)と.catch()を呼び出すことができます。

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3)Async / Await:新しい構文ES6-Awaitは基本的にPromiseの砂糖構文です!

非同期関数は、promiseで得られるのと同じ結果を達成するために、より少ないコードを記述できるようにするクリーンで簡潔な構文を提供します。Async / Awaitは同期コード似ており、同期コードは読み取りと書き込みがはるかに簡単です。Async / Awaitのエラーをキャッチするには、ブロックを使用できますtry...catch。ここでは、Promise構文の.then()のチェーンを作成する必要はありません。

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

結論:これらは、JavaScriptでの非同期プログラミングの3つの構文であり、よく理解している必要があります。したがって、可能であれば、非同期コードのリファクタリングには「promise」または「async / await」を使用することをお勧めします(主にXHRリクエストの場合)

20
Matthew Brent 2018-05-05 05:56.

コードをスローするのではなく、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は、続行する前に応答に依存します。

大きな問題は、いつ応答が得られるかということです。答えはわかりません。そのため、イベントループは、そのメッセージが「heyrunme」と言うのを待っています。JSがそのメッセージを同期的に待っていた場合、アプリはフリーズし、最悪の事態になります。したがって、JSは、メッセージがキューに追加されるのを待っている間、キュー内の次のアイテムの実行を続行します。

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

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

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

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

そのため、ほとんどの場合(常にではありませんが)合格しfooませんfoo()

うまくいけば、それはある程度意味があるでしょう。このような混乱を招くような事態に遭遇した場合は、少なくともそれを理解するために、ドキュメントを完全に読むことを強くお勧めします。それはあなたをはるかに優れた開発者にするでしょう。

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ジェフ・トゥイーディーはトロルに直接話しかけ、「温かい怒りの料理」を約束します

ジェフ・トゥイーディーはトロルに直接話しかけ、「温かい怒りの料理」を約束します

写真:Mark Metcalfe / Getty Imagesバンドのファンに感謝している限り、JeffTweedyはWilcoのFacebookページを政治のないフォーラムに変えようとはしていません。フロントマンは昨日、ウィルコがソーシャルメディアの存在から州と世界の問題を遠ざけることを望んでいるトロールに転向した(またはおそらく彼らはいつもそうだった)コメント投稿者に応えてオンラインの文書を投稿した。

弾丸、バダス、そして赤ちゃん:ジョンウーの見事なピストルオペラはそれをすべて持っています

弾丸、バダス、そして赤ちゃん:ジョンウーの見事なピストルオペラはそれをすべて持っています

ハードボイルドジョンウーの1992年の傑作であるハードボイルドの数分後、2人のガンランナーが、人々が鳥かごを運ぶ香港の喫茶店の1つから逃げようとします。彼らは取引をしているが、彼らを破滅させるために数人のスーパーコップが現れ、彼らはただ逃げようとしている。

ローマ経済は大国でした

ローマ経済は大国でした

写真提供者:Clive Brunskill / Gettyローマ帝国のことを非常に多く覚えています。剣闘士は常に人気があり、軍隊はすぐ近くにありますが、ローマで最も印象的なのは、残された建物の記念碑的な風景です。

ホーク+ハチェットキャンドルで森を家に持ち帰る

ホーク+ハチェットキャンドルで森を家に持ち帰る

Hawk + ​​Hatchet Hawk + ​​Hatchetキャンドルは、キャンプファイヤー、キャビン、港町の思い出をとらえるために、手で注がれ、混ぜられた小さなバッチです。ろうそくは見栄えが良く、匂いもさらに良く、圧倒されることなく部屋を満たし、意図されたシーンをうまく呼び起こします。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

安全な公衆パニックルーム

安全な公衆パニックルーム

不気味な音に慣れて、コンクリートの周りをうろつきます。痛みや苦い口を吐き出し、「過ち」の不思議な病気を通してもう一度味わうことを切望します。

長い間行方不明だった 2 つのサンフランシスコ クリークが間もなく日の目を見る可能性がある

都市の水路を回復する理由はたくさんありますが、気候が変化するにつれて、それらはすべてより持続可能な未来につながります.

長い間行方不明だった 2 つのサンフランシスコ クリークが間もなく日の目を見る可能性がある

サンフランシスコの長い間失われた小川を復元する動きが高まっており、コミュニティを地域の生態系と結びつけ、また、都市が気候変動や季節的な洪水に適応するのに役立つ新しいタイプの「グリーン インフラストラクチャ」を作成しています。最新の取り組みは町の 2 つの非常に異なる場所で行われており、それらは明確ではあるが補完的な方法で展開されています。

サンフランシスコのベイエリアで家族写真が撮れる場所トップ 5

サンフランシスコのベイエリアで家族写真が撮れる場所トップ 5

ベイエリアには写真を撮るための美しい屋外スポットがたくさんあります。この記事のすべての写真は、ベイエリアにいるさまざまな PictureHum フォトグラファーとの PictureHum セッションからのものです。

退屈に耐えられないから生きていけない

退屈に耐えられないから生きていけない

それが現代の衝動です — より少ないもので世界を覆し、私たちのほんのわずかな瞬間を自分たちから盗もうとする. 私はかつてダイニングルームのソファで一日を過ごし、祖父母の農家に斜めに差し込む光を眺め、私には関係のない世界について話している大人のつぶやきを半分聞いたり、壁を通してくぐもったテレビを聞いたり、遊んだりしました。床と犬と私の小さな靴を暖めた太陽の黒点と。

Language