6298

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

then約束のブロックを使用した例:

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`
}
4

42 に答える 42

6302

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

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

問題

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つの異なるソリューションを見ていきます。

  • Promises withasync/await(ES2017 +、トランスパイラーまたはリジェネレーターを使用している場合は古いブラウザーで利用可能)
  • コールバック(ノードで人気)
  • Promise withthen()(ES2015 +、多くのPromiseライブラリの1つを使用している場合は古いブラウザで利用可能)

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


ES2017 +:約束async/await

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

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

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

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

上記の遅延関数を詳しく説明する例を次に示します。findItem()

// 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()で約束

PromiseAPIはECMAScript6(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).
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

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("https://jsonplaceholder.typicode.com/todos/1")
  .then(function(result) {
    console.log(result); // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

promiseが提供するすべての利点を説明することは、この回答の範囲を超えていますが、新しいコードを作成する場合は、それらを真剣に検討する必要があります。それらはあなたのコードの素晴らしい抽象化と分離を提供します。

約束に関する詳細情報:HTML5の岩-JavaScriptの約束

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

遅延オブジェクトは、jQueryのpromiseのカスタム実装です(Promise APIが標準化される前)。それらはほとんどpromiseのように動作しますが、わずかに異なる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」ページをチェックしている間はコードをフリーズしません。サーバーにリクエストを送信し、待機している間、サーバーからの応答ではなく、jQueryAjaxDeferredオブジェクトをすぐに返します。つまり、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以降で非推奨になっていることに注意してください。その後、コールバックを使用するか、 jqXHRオブジェクトのプロパティにsuccessアクセスできます。responseText

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

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

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

于 2013-01-08T17:06:14.013 に答える
1167

コードで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スタートガイドをお読みください。

于 2013-05-29T23:30:56.593 に答える
428

XMLHttpRequest 2(まず、 BenjaminGruenbaumFelixKlingからの回答を読んでください)

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のヘッダーを取得する場合にのみ役立ちます。

XMLHttpRequestonload / 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)
}

繰り返しますが...これは非常に短い関数ですが、GETとPOSTを実行します。

使用例:

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番目のケースでは、それは単に機能します。正しい投稿データを渡したかどうかをサーバー側で確認する必要があります。

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

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

エラー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などをWebSQLデータベースに入力しました...

しかし、これらの場合、その目的のためだけに関数を作成する必要があります...時にはblob、配列バッファーが必要であり、ヘッダーを設定し、mimetypeをオーバーライドすることができ、さらに多くのことがあります...

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

于 2013-08-19T08:06:04.867 に答える
350

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

これは、AngularJS、jQuery(遅延あり)、ネイティブXHRの置換(フェッチ)、Ember.jsBackbone.jsの保存、またはpromiseを返す任意のNode.jsライブラリを意味します。

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

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の一般的な使用法に関するものです。


コアの問題

ブラウザとNode.js/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、サーバー呼び出し、I / O、タイマー)はまだ行われていないため、リクエストがコードにその値を通知する前に、値を返しています。

この問題に対する考えられる解決策の1つは、計算が完了したときにプログラムに何をすべきかをプログラムに指示して、リアクティブにコーディングすることです。Promiseは、本質的に時間的(時間に敏感)であることにより、これを積極的に可能にします。

約束の簡単な要約

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

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

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

.then約束を呼び出してそこから何かを返すと、処理された値の約束が得られます。私たちが別の約束を返すならば、私たちは素晴らしいものを手に入れるでしょう、しかし私たちの馬を抱きしめましょう。

約束を持って

上記の問題をpromiseで解決する方法を見てみましょう。まず、遅延関数を作成するためにPromiseコンストラクターを使用して、上からのpromise状態の理解を示しましょう。

function delay(ms){ // Takes amount of milliseconds
    // Returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // When the time is up,
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

これで、 setTimeoutをpromiseを使用するように変換した後、thenそれをカウントするために使用できます。

function delay(ms){ // Takes amount of milliseconds
  // Returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // When the time is up,
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // We're RETURNING the promise. Remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // When the promise is ready,
      return 5; // return the value 5. Promises are all about return values
  })
}
// We _have_ to wrap it like this in the call site, and we can't access the plain value
getFive().then(function(five){
   document.body.innerHTML = five;
});

基本的に、並行性モデルのために実行できないを返す代わりに、でアンラップできる値のラッパーを返します。で開ける箱のようなものです。thenthen

これを適用する

これは、元の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, Nodes.js, and io.js only
    yield 1;
    yield 2;
    while(true) yield 3;
}

反復可能なシーケンスに対してイテレータを返す関数です。1,2,3,3,3,3,....これはそれ自体が興味深いものであり、多くの可能性の余地がありますが、特に興味深いケースが1つあります。

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

これはややトリッキーですが、非常に強力なトリックで、非同期コードを同期的に記述しましょう。あなたのためにこれを行ういくつかの「ランナー」がいます。1つを書くことは短い数行のコードですが、それはこの答えの範囲を超えています。ここではBluebirdを使用しますが、またはPromise.coroutineのような他のラッパーがあります。coQ.async

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // Notice the yield
    // The 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
   // The server call is done here, and the 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 are done
});
main();

ES2016(ES7)

ES7では、これはさらに標準化されています。現在、いくつかの提案がありますが、それらすべてでawait約束することができます。asyncこれは、 andawaitキーワードを追加することによる、上記の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
}

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

于 2015-05-12T02:22:57.533 に答える
276

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

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

于 2014-05-23T02:05:01.057 に答える
265

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

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

于 2016-08-11T14:17:36.010 に答える
256

最も簡単な解決策は、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 a 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);
});
于 2014-02-18T18:58:37.407 に答える
177

Angular 1

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

ここにそれは言う、

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以降

Angular 2では、次の例を見てください。ただし、 Angular2でオブザーバブルを使用することをお勧めします。

 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の仕様は次のとおりです。

于 2014-08-26T08:11:36.220 に答える
176

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

// 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:", JSON.stringify(results)); // E.g., using the results
        }
    });
});

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

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

index結果が順不同で到着した場合でも、 fromを使用して、関連するエントリと同じ位置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:", JSON.stringify(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:", JSON.stringify(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(または、代わりに、promiseを返すラッパーを作成してから、以下を実行することもできます...)

あなたに約束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);
});

2番目と3番目の引数を無視することがわかっている場合はdoSomethingAsync、直接渡すことができます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:", JSON.stringify(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:", JSON.stringify(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、ES2017 +構文を使用できる場合(おそらくBabelのようなトランスパイラーで)、および:でasync関数を使用できます。for-ofawait

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:", JSON.stringify(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 +構文を(まだ)使用できない場合は、「Promisereduce」パターンのバリエーションを使用できます(これは、結果を次の1つに渡さないため、通常のPromisereduceよりも複雑です。代わりに結果を配列にまとめる):

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:", JSON.stringify(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:", JSON.stringify(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; }

于 2017-05-03T16:59:06.400 に答える
122

この例を見てください:

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);
  });
})();
于 2016-06-02T08:31:13.677 に答える
121

これは、多くの新しいJavaScriptフレームワークで使用されている双方向のデータバインディングまたはストアの概念が最適に機能する場所の1つです...

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

しかし、この質問で尋ねたように、たとえば純粋なJavaScriptまたはjQueryでそれをどのように行うことができますか?

コールバック、promise、および最近監視可能なコールバックを使用して、それを処理できます。たとえば、promiseには、データの準備ができたときに実行される、success()または実行される関数があります。observablethen()のコールバックまたはサブスクライブ関数でも同じです。

たとえば、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(); // The call happens here
});

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

于 2017-05-24T09:38:10.597 に答える
118

これは、JavaScriptの「謎」に苦しんでいるときに直面する非常に一般的な問題です。今日はこの謎を解き明かしてみましょう。

簡単なJavaScript関数から始めましょう:

function foo(){
    // Do something
    return 'wohoo';
}

let bar = foo(); // 'bar' is 'wohoo' here

これは単純な同期関数呼び出しであり(コードの各行は、次の行の前に「ジョブで終了」します)、結果は期待どおりです。

次に、関数に少し遅延を導入して、コードのすべての行が順番に「終了」しないように、少しひねりを加えましょう。したがって、関数の非同期動作をエミュレートします。

function foo(){
    setTimeout( ()=> {
        return 'wohoo';
   }, 1000)
}

let bar = foo() // 'bar' is undefined here

さあ、行きましょう。その遅延は、私たちが期待していた機能を壊してしまいました。しかし、正確には何が起こったのでしょうか?コードを見ると、実際にはかなり論理的です。

関数foo()は、実行時に何も返しません(したがって、戻り値はundefined)が、タイマーを開始します。タイマーは、1秒後に関数を実行して「wohoo」を返します。しかし、ご覧のとおり、barに割り当てられる値は、foo()からすぐに返されるものであり、これは何もありません。つまり、。だけundefinedです。

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

私たちの関数に約束を求めましょう。Promiseは、実際にはそれが何を意味するかについてです。つまり、関数は、将来取得する出力を提供することを保証します。それでは、上記の小さな問題の実際の動作を見てみましょう。

function foo(){
   return new Promise((resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){
      // Promise is RESOLVED, when the 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つのアプローチがあります。アイデアは、非同期関数を認識し、promiseが解決するのを待ってから、次のコード行に移動することです。それはまだ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);
     }
  }
于 2017-10-31T20:12:45.767 に答える
109

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

同じ例を次に示します。

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

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

于 2015-09-02T12:54:37.763 に答える
96

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

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
}

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

于 2016-01-25T17:43:56.100 に答える
93

私が書いた次の例は、

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

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

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

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

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

次に、ネストされた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" />

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

于 2016-04-12T22:55:39.063 に答える
92

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

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

$.ajax({
    url: "...",
    success: callback
});
于 2016-04-22T14:47:08.960 に答える
87

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

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

  1. イベントループ

  2. Web API

  3. イベントキュー

イベントループは永久に実行されます。つまり、一種の無限ループです。イベントキューは、すべての関数が特定のイベントにプッシュされる場所です(例:クリック)。

これは、キューから1つずつ実行され、この関数を実行するイベントループに入れられ、最初の関数が実行された後、次の関数の準備をします。つまり、1つの関数の実行は、キュー内の関数がイベントループで実行される前の関数まで開始されません。

ここで、2つの関数をキューに入れたとしましょう。1つはサーバーからデータを取得するためのもので、もう1つはそのデータを利用するためのものです。最初にserverRequest()関数をキューにプッシュし、次にutiliseData()関数をプッシュしました。serverRequest関数はイベントループに入り、サーバーからデータを取得するのにかかる時間がわからないため、サーバーを呼び出します。このプロセスには時間がかかると予想されるため、イベントループがビジー状態になり、ページがハングします。

そこで、WebAPIがその役割を果たします。この関数をイベントループから取得し、サーバーを処理してイベントループを解放し、キューから次の関数を実行できるようにします。

キュー内の次の関数はutiliseData()であり、ループに入りますが、使用可能なデータがないため、無駄になり、次の関数の実行はキューの最後まで続行されます。(これは非同期呼び出しと呼ばれます。つまり、データを取得するまで他のことを行うことができます。)

serverRequest()関数のコードにreturnステートメントが含まれていると仮定します。サーバーのWebAPIからデータを取得すると、キューの最後にあるキューにデータがプッシュされます。

キューの最後にプッシュされるため、このデータを利用するための関数がキューに残っていないため、そのデータを利用できません。したがって、非同期呼び出しから何かを返すことはできません。

したがって、これに対する解決策コールバックまたは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");
}

JavaScript.infoコールバック

于 2018-02-03T06:06:53.637 に答える
85

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

これは非常に簡単です。

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

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

(async function(){

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

})()

awaitは、現在のすべてのブラウザーとNode.js8でサポートされています

于 2017-06-02T09:51:10.950 に答える
71

このカスタムライブラリ(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)
});
于 2016-05-26T13:26:40.817 に答える
71

別の解決策は、シーケンシャルエグゼキュータ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は、すべての演算子と式を段階的に評価し、遅い関数の結果が準備できていない場合に備えて実行を一時停止します。

その他の例はこちらです。

于 2017-05-27T02:47:04.280 に答える
44

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);
于 2018-02-17T15:26:28.153 に答える
41

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

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

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

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

JavaScriptには、実行する前に終了するのを待つ必要があることを知る方法がないためです。言い換えれば、それが非同期であるかどうかはわかりません-将来のある時までミルクをもたらさないものです。JavaScriptやその他の宣言型言語は、待たずに次々とステートメントを実行します。order_milkput_in_coffeeorder_milk

この問題に対する従来のJavaScriptのアプローチは、JavaScriptが関数を渡すことができるファーストクラスのオブジェクトとしてサポートしているという事実を利用して、関数をパラメーターとして非同期要求に渡し、それが完了すると呼び出します。将来のいつかそのタスク。それが「コールバック」アプローチです。次のようになります。

order_milk(put_in_coffee);

order_milkキックオフし、ミルクを注文し、到着したときにのみ、を呼び出しますput_in_coffee

このコールバックアプローチの問題は、結果を報告する関数の通常のセマンティクスをreturn;で汚染することです。代わりに、関数は、パラメーターとして指定されたコールバックを呼び出して結果を報告してはなりません。また、このアプローチは、イベントのより長いシーケンスを処理するときにすぐに扱いにくくなる可能性があります。たとえば、ミルクがコーヒーに入れられるのを待ってから、3番目のステップであるコーヒーを飲むことを実行したいとします。私は次のようなものを書く必要があります:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

put_in_coffeeここで、ミルクを入れるためのミルクと、ミルクを入れた後に実行するアクション()の両方を渡しdrink_coffeeます。このようなコードは、書き込み、読み取り、およびデバッグが困難になります。

この場合、質問のコードを次のように書き直すことができます。

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

約束を入力してください

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

ミルクとコーヒーの場合、order_milk到着するミルクの約束を返すように設計し、次のようにアクションput_in_coffeeとして指定します。then

order_milk() . then(put_in_coffee)

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

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

あなたの特定の問題に約束を適用しましょう。リクエストロジックを関数内にラップします。これにより、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); });

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

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

asyncキーワード_

しかし、同期の場合は一方向に、非同期の場合はまったく異なる方法でコードを記述しなければならないことについて、漠然と不満があります。同期の場合、次のように記述します

a();
b();

しかし、a非同期の場合、私たちが書かなければならない約束があります

a() . then(b);

上記で、「JavaScriptには、最初の呼び出しが終了するのを待ってから2番目の呼び出しを実行する必要があることを知る方法がありません」と述べました。JavaScriptにそれを伝える方法あればいいと思いませんか?await「非同期」関数と呼ばれる特別なタイプの関数内で使用されるキーワードがあることがわかりました。この機能は、ECMAScript(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);
}
于 2016-01-23T03:28:39.267 に答える
40

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

toを呼び出しても、foo()有用なものは何も返されないことに注意してください。ただし、非同期呼び出しの結果はに保存されresult.responseます。

于 2015-09-23T22:52:03.907 に答える
40

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

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

于 2016-08-13T09:36:41.877 に答える
39

成功の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();
于 2017-04-24T08:09:03.637 に答える
34

1.最初のつまずきのステップ

他の多くの人と同様に、非同期呼び出しとの出会いは最初は不可解でした。
詳細は覚えていませんが、次のようなことを試した可能性があります。

let result;

$.ajax({
  url: 'https://jsonplaceholder.typicode.com/todos/1',
  success: function (response) {
    console.log('\nInside $.ajax:');
    console.log(response);
    result = response;
  }
});

console.log('Finally, the result: ' + result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

おっと!最後console.log('Finally, the result: ' + result);に 印刷されると思っていた行の出力は、 実際には他の出力の前に印刷されます。–結果は含まれていません。印刷するだけです。 1 どうして?undefined

役立つ洞察

私は最初のああをはっきりと覚えています!非同期呼び出しを理解する方法についての瞬間。このコメント
は次のように述べています。実際には、コールバックからデータを取得したくない。 データを必要とするアクションコールバックに入れたい!2 これは上記の例で明らかです。しかし、応答が完了した後の応答を処理する非同期呼び出しのに コードを書くことはまだ可能ですか?



2.プレーンJavaScriptとコールバック関数

答えはイエスです!- 可能です。
1つの代替方法は、継続渡しスタイルでのコールバック関数 の使用です。3

const url = 'https://jsonplaceholder.typicode.com/todos/2';

function asynchronousCall (callback) {
  const request = new XMLHttpRequest();
  request.open('GET', url);
  request.send();
  request.onload = function () {
    if (request.readyState === request.DONE) {
      console.log('The request is done. Now calling back.');
      callback(request.responseText);
    }
  };
}

asynchronousCall(function (result) {
  console.log('This is the start of the callback function. Result:');
  console.log(result);
  console.log('The callback function finishes on this line. THE END!');
});

console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper { max-height: 100% !important; top: 0; }

asynchronousCall関数がどのようになっているかに注意してくださいvoid。何も返しません。代わりにasynchronousCall、匿名のコールバック関数(asynchronousCall(function (result) {...)を使用して呼び出すことにより、この関数は結果に対して目的のアクションを実行しますが、要求が完了したresponseText、つまりが使用可能な場合に限ります。

上記のスニペットを実行すると、非同期呼び出し(行など)の にコードを記述したくないことがわかります LAST in the code, but executed FIRST!
なんで?–このようなコードは、非同期呼び出しが応答データを配信する前に発生するためです。
そうすることで、コード出力と比較するときに混乱が生じることになります。

.then()3. –またはasync/で約束するawait

.then()コンストラクトは2015年6月にECMA-262第6版で導入され、async/awaitコンストラクトは2017年6月にECMA-262第8版で導入されました。
以下のコードはまだプレーンなJavaScriptであり、古い XMLHttpRequestFetchに置き換えています。 4

fetch('http://api.icndb.com/jokes/random')
  .then(response => response.json())
  .then(responseBody => {
    console.log('.then() - the response body:');
    console.log(JSON.stringify(responseBody) + '\n\n');
  });

async function receiveAndAwaitPromise () {
  const responseBody =
    (await fetch('http://api.icndb.com/jokes/random')).json();
  console.log('async/await:');
  console.log(JSON.stringify(await responseBody) + '\n\n');
}

receiveAndAwaitPromise();
.as-console-wrapper { max-height: 100% !important; top: 0; }

async/await 構文を使用する場合は、警告が表示されます。上記のスニペットでは、 2つの場所でどのようawaitに必要であるかに注意してください。そもそも忘れてしまうと出力が出なくなります。2番目に忘れた場合、出力は空のオブジェクト (またはまたは)のみになります。 関数のプレフィックスを忘れることは、おそらくすべての中で最悪です–出力は–欠落しているキーワードについての言及はありません。{}[object Object][object Promise]
async"SyntaxError: missing ) in parenthetical" async

4. Promise.all –URLの配列5

大量のURLをリクエストする必要があるとします。1つのリクエストを送信し、応答するまで待ってから、次のリクエストを送信し、応答するまで待つ、というように…
ああ!–それは長い時間がかかる可能性があります。それらをすべて一度に送信して、最も遅い応答が到着するのにかかる時間より長く待たないほうがよいのではないでしょうか。

簡単な例として、以下を使用します。

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

2つのURLのJSON:

{"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false}
{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false}

目標は、オブジェクトの配列を取得することです。各オブジェクトにはtitle 、対応するURLからの値が含まれています。

もう少し面白くするために、 URLの結果(タイトル)の配列をマージしたい名前の配列がすでにあると仮定します。

namesonly = ['two', 'three']

必要な出力は、オブジェクトの配列にnamesonly結合さurlsれ たマッシュアップです。

[{"name":"two","loremipsum":"quis ut nam facilis et officia qui"},
{"name":"three","loremipsum":"fugiat veniam minus"}]

ここで、の名前をに変更しtitleましたloremipsum

const namesonly = ['two','three'];

const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => {
    const names = namesonly.map(value => ({ name: value }));
    console.log('names: ' + JSON.stringify(names));
    const latins = titles.map(value => ({ loremipsum: value }));
    console.log('latins:\n' + JSON.stringify(latins));
    const result =
      names.map((item, i) => Object.assign({}, item, latins[i]));
    console.log('result:\n' + JSON.stringify(result));
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

上記の例はすべて短く、おもちゃのAPIで非同期呼び出しを使用する方法を簡潔に示しています。小さなAPIを使用すると、概念と動作するコードを説明するのに適していますが、例は少しドライランになる可能性があります。

次のセクションでは、APIを組み合わせてより興味深い出力を作成する方法についてのより現実的な例を示します。

5.Postman6でマッシュアップを視覚化する方法

MusicBrainz API には、アーティストと音楽バンドに関する情報があります。
例–英国のロックバンドColdplayのリクエストは次のとおりです:http:
//musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?& fmt = json&inc = url-rels+release-groups 。
JSON応答には、特に、バンドによる25の初期のアルバムタイトルが含まれています。この情報はrelease-groups配列にあります。最初のオブジェクトを含むこの配列の開始は次のとおりです。

...
  "release-groups": [
    {
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    },
...

このJSONスニペットは、Coldplayの最初のアルバムがパラシューツであることを示しています。またid、この場合1dc4c347-a1db-32aa-b14f-bc9cc507b843、アルバムの一意の識別子であるを提供します。

この識別子は、Cover Art Archive APIでルックアップを行うために使用できます:http :
//coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b8437

アルバムごとに、JSON応答にはいくつかの画像が含まれ、そのうちの1つはアルバムの表紙です。上記のリクエストに対する応答の最初の数行:

{
  "images": [
    {
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": {
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    },
...

ここで興味深いのは行 "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"です。そのURLは、パラシューツアルバム
の表紙への直接リンクです。

マッシュアップを作成して視覚化するためのコード

全体的なタスクは、Postmanを使用して、音楽バンドのすべてのアルバムタイトルと表紙を視覚化することです。これを実現するためのコードの記述方法は、PostmanでAPIマッシュアップを視覚化するにはどうすればよいですか?という質問への 回答ですでにかなり詳細に説明されてい ます。–したがって、ここでは長い議論を避け、コードと結果のスクリーンショットを提示するだけです。

const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) {
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  }
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => {
    asynchronousCall(url, imageURL => {
      images[index] = imageURL;
      if (--countDown === 0) { // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ({ title: value }));
        const albumImages = images.map(value => ({ image: value }));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign({}, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          {{#each responseI}}
          <tr><td>{{title}}<br><img src="{{image}}"></td></tr>
          {{/each}}
        </table>`;
        pm.visualizer.set(template, { responseI: albumsAndImages });
      }
    });
  });
  function asynchronousCall (url, callback) {
    pm.sendRequest(url, (_, responseI) => {
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    });
  }
});


結果とドキュメント

Postmanの結果とドキュメント


Postmanコレクションをダウンロードして実行する方法

Postmanコレクションの実行は簡単なはずです。デスクトップ版のPostman
を使用していると仮定して、次のようにします。


  1. http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.jsonをダウンロード
    して、ハードドライブの適切な場所に保存します。

  2. Postmanで、Ctrl+ O>ファイルのアップロード MusicBands.pm_coll.json>>インポート。これで、Postmanのコレクションの中に
    表示されるはずです。MusicBands

  3. コレクションMusicBands>>>送信DummyRequest_ 8

  4. Postman Response Bodyで、[視覚化]をクリックします。

  5. これで、上のスクリーンショットに示されているように、15枚のアルバムをスクロールできるようになります。

参考文献


1元のポスターで次のように表現されています:それらはすべて戻り undefinedます。
2非同期呼び出しが混乱していると思われる場合は、非同期呼び出しに関するいくつかの質問と回答を調べて、それが役立つかどうかを確認することを検討してください。
3この名前は AJAXのXXMLHttpRequestと同じくらい誤解を招くものです。最近、WebAPIのデータ形式はXMLではなくJSONです。4FetchはPromise を返しますXMLHttpRequestFetchもECMAScriptの一部ではないことを知って驚いた
標準。JavaScriptがここでそれらにアクセスできる理由は、Webブラウザがそれらを提供するためです。 FetchStandardXMLHttpRequestStandardは、どちらも 2004年6月に結成されたWeb Hypertext Application Technology Working Group(WHATWG)によって支持されています
。5このセクションでは、 Promise.allを使用してURLの配列をフェッチするにはどうすればよいですか。
6このセクションは 、PostmanでAPIマッシュアップを視覚化するにはどうすればよいですか?
7このURLは自動的にhttps://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.jsonにリダイレクトされ ます。
8エラーが発生した場合は、 スクリプトの実行中に問題が発生しました。もう一度[送信]をクリックしてみてください。

于 2021-05-22T10:24:15.720 に答える
31

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が定義されていません

そこで、ES3用の独自のPromiseクラスを、定義されていない場合はJavaScriptコンパイラーの下に実装することにしました。メインコードの前にこのコードを追加するだけで、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;
    }();
}
于 2018-12-07T14:10:23.677 に答える
30

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

したがって、コードスニペットは少し異なるように書き直すことができます。

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
于 2017-07-05T20:28:42.720 に答える
30

質問は:

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

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

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

解決策は、コールバックを回避し、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")
于 2018-01-13T19:13:20.800 に答える
28

コードを投げかけるのではなく、JavaScriptがコールバックと非同期性をどのように処理するかを理解するための鍵となる2つの概念があります(それは単語でさえありますか?)

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

知っておく必要のあることが3つあります。待ち行列; イベントループとスタック

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

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

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

  1. foob​​arFuncを使用してfoo.com/api/barを呼び出します
  2. 無限ループを実行してください...など

これらのメッセージの1つが実行されると、キューからメッセージがポップされてスタックが作成されます。スタックは、JavaScriptがメッセージ内の命令を実行するために実行する必要のあるすべてのものです。したがって、この例では、呼び出すように指示されていますfoobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

したがって、foobarFuncが実行する必要のあるもの(この場合anotherFunction)はすべてスタックにプッシュされます。実行された後、忘れられた-イベントループはキュー内の次のものに移動します(またはメッセージをリッスンします)

ここで重要なのは、実行の順序です。あれは

何かが実行されるとき

AJAXを使用して外部のパーティに電話をかけるか、非同期コード(たとえば、setTimeout)を実行すると、JavaScriptは、続行する前に応答に依存します。

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

そのため、非同期機能ではコールバックと呼ばれるものを使用します。-別の関数に渡されたときに後日実行される関数またはハンドラー。promise.then()は、この非同期動作をより直線的な方法で推論する方法として、コールバック(たとえば、渡される関数)を使用します。約束は「ある時点で何かを返すことを約束する」という言い方であり、コールバックは最終的に返される値を処理する方法です。deffered.done deffered.failjQueryは、and deffered.always(とりわけ)と呼ばれる特定のコールバックを使用します。あなたはそれらすべてをここで見ることができます

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

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

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

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

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

于 2018-05-04T15:56:07.643 に答える
27

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

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インスタンスです。解決関数は、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のシンタックスシュガーです!

Async関数は、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リクエストの場合)

于 2020-01-19T22:23:07.037 に答える
26

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)

})

上記のコードを示すスタックスニペット。

// The function declaration:
async function foo() {
  var response = await $.ajax({
    url: 'https://jsonplaceholder.typicode.com/todos/1'
  })
  return response;
}

// Execute it like this:
(async function() {
  try {
    var result = await foo()
    console.log(result)
  } catch (e) {}
})()

// Or use Promise syntax:
foo().then(response => {
  console.log(response)
}).catch(error => {
  console.log(error)
})
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

于 2018-01-24T06:18:55.310 に答える
24

動作する例を次に示します。

const validateName = async userName => {
  const url = "https://jsonplaceholder.typicode.com/todos/1";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>

于 2018-07-12T21:48:15.560 に答える
19

待つ

リクエストは非同期で機能するため、通常のコードのようにデータを同期的に読み取ることはできません。ただし、を使用async/awaitすると、通常の同期/シーケンシャルスタイルに近い/類似したように見える非同期コードを作成できます。応答データを処理するコードは、async関数(以下のスニペット)でラップする必要があり、その中にキーワードloadを追加する必要があります(これも使用します)。awaitfoo()async/await

async function foo() {
  var url = 'https://jsonplaceholder.typicode.com/todos/1';
  var result = (await fetch(url)).text(); // Or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();

関数は常に(暗黙的に)その結果をpromiseにラップすることを忘れないでくださいasync(したがって、promiseを返します)。

于 2019-06-11T07:56:10.160 に答える
15

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

ここには非常に詳細な有益な回答がたくさんありますが、それらのいずれも繰り返さないことにします。JavaScriptでプログラミングするための鍵は、最初に全体的な実行の正しいメンタルモデルを持つことです。

  1. エントリポイントは、イベントの結果として実行されます。たとえば、コードを含むスクリプトタグがブラウザに読み込まれます。(したがって、これが、DOM要素を最初に構築する必要がある場合などにコードを実行するためのページの準備に注意を払う必要がある理由です。)
  2. コードは、XHRリクエスト、タイムアウトの設定、DOMイベントハンドラーなどのコールバックを実行せずに、コードが完了するまで実行れます。実行されるのを待っているコールバックはそれぞれ、キューに入れられて待機します。発生した他のイベントがすべて実行を終了した後に実行される順番。
  3. XHRリクエストへの個々のコールバック、タイムアウトまたはDOMの設定は、一度呼び出されたイベントが完了するまで実行されます。

良いニュースは、この点をよく理解していれば、競合状態について心配する必要がないということです。何よりもまず、コードを本質的にさまざまな離散イベントへの応答として編成する方法と、それらを論理シーケンスにスレッド化する方法を検討する必要があります。そのためのツールとして、promiseまたはより高いレベルの新しいasync / awaitを使用することも、独自のツールとして使用することもできます。

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

于 2017-10-25T03:22:21.970 に答える
11

関数からAjax応答の結果を直接返す方法はありません。その理由は、Ajax呼び出し($.get()または$.post())が非同期であり、Ajax呼び出しをカプセル化する関数を呼び出すと、応答がレンダリングされる前でも返されるためです。

このようなシナリオでは、唯一のオプションは、応答が到着したときに解決されるpromiseオブジェクトを返すことです。

上記の問題を解決するには2つの方法があります。どちらも約束を利用します。

以下のコードスニペットには、JSONURLが含まれています。どちらも機能し、 JSFiddleに直接コピーしてテストできます。

オプション#1-fooメソッドから直接Ajax呼び出しを返します。
jQueryの最新バージョンでは、Ajax呼び出しはpromiseオブジェクトを返します。これは、.then関数を使用して解決できます。コードでは、この場合.then、関数の前に解決されるコールバック関数がありますfoo()

   // Declare function foo
   function foo(url)
   {
     return $.get(url);
   }

   // Invoke the foo function, which returns a promise object
   // the 'then' function accepts the call back to the resolve function
   foo('https://jsonplaceholder.typicode.com/todos/1')
     .then(function(response)
     {
       console.log(response);
     })
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

オプション#2-promiseオブジェクトを宣言して返します。
関数内でpromiseオブジェクトを宣言し、そのpromise関数内でAjax呼び出しをカプセル化して、promiseオブジェクトを返します。

   function foo1() {
     var promise = new Promise(function(resolve, reject)
     {
       $.ajax({
       url: 'https://jsonplaceholder.typicode.com/todos/1',
       success: function(response) {
           console.log(response);
           resolve(response);
           // return response; // <- I tried that one as well
         }
       });
     });
     return promise;
   }

   foo1()
   .then(function(response)
   {
     console.log('Promise resolved:');
     console.log(response);
   })
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

于 2020-03-03T06:25:29.670 に答える
7

使用する方法やメカニズム、またはそれを隠すフレームワーク(Angular / React)に関係なく、次の原則が当てはまると思います。

  1. プログラムの流れ(コードや最下位レベル:マシンコードを考えてください)では、データが2秒後、3秒後に戻ってこない、またはまったく届かない可能性があるため、通常は次のreturn目的で使用する必要はありません。データを返します。

  2. これは、古典的な「オブザーバーパターン」です。(「コールバック」の形式にすることもできます。)「データが正常に到着したことを知りたいのですが、到着したらお知らせください。」したがって、通知対象のオブザーバー(またはデータの到着が成功したことを通知するために呼び出される関数)を登録します。通常、このようなデータの到着の失敗についてオブザーバーを登録します。

  3. データの到着が成功した場合、またはそのようなデータの返送が失敗した場合、登録されたオブザーバー(またはコールバック)はデータと一緒に通知されます(またはデータとともに呼び出されます)。オブザーバーがコールバック関数の形式で登録されている場合は、foofoo(data)呼び出されます。オブザーバーがオブジェクトの形式で登録されている場合foo、インターフェースによっては、それfoo.notify(data)が呼び出される可能性があります。

于 2019-09-10T07:25:53.897 に答える
7

もともと、コールバックは非同期操作に使用されていました(たとえば、XMLHttpRequest APIで)。現在、ブラウザーのFetch APIなどのpromiseベースのAPIがデフォルトのソリューションになり、より優れたasync/await構文がすべての最新のブラウザーとNode.js(サーバー側)でサポートされています。

一般的なシナリオ(サーバーからJSONデータをフェッチする)は次のようになります。

async function fetchResource(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  return res.json();
}

別の関数で使用するには:

async function doSomething() {
  try {
    const data = await fetchResource("https://example.test/resource/1");
    // ...
  } catch (e) {
    // Handle error
    ...
  }
}

最新のAPIを設計する場合は、コールバックよりもPromiseベースのスタイルを優先することを強くお勧めします。コールバックに依存するAPIを継承した場合、それをpromiseとしてラップすることができます。

function sleep(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

async function fetchAfterTwoSeconds(url) {
  await sleep(2000);
  return fetchResource(url);
}

歴史的にコールバックのみに依存していたNode.jsでは、その手法は非常に一般的であるため、。と呼ばれるヘルパー関数が追加されましたutil.promisify

于 2020-05-12T09:56:33.080 に答える
2

Babelasync/awaitのようなトランスパイラーで使用して、古いブラウザーで機能させる。また、このBabelプリセットとnpmからのポリフィルをインストールする必要があります。npm i -D babel-preset-env babel-polyfill

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

async test() {
  try {
    const res = await getData('https://api.icndb.com/jokes/random')
    console.log(res)
  } catch(err) {
    console.log(err);
  }
}

test();

または、.thenコールバックは同じロジックを作成するための単なる別の方法です。

getData(ajaxurl).then(function(res) {
    console.log(res)
}
于 2019-04-16T16:13:16.973 に答える
2

非同期:false

asyncfalseに設定し、Ajax呼び出しを再構築することで解決しました。

sendRequest(type, url, data)どこでも毎回呼び出されるように、3つのパラメーターで呼び出されるグローバル関数を設定しました。

function sendRequest(type, url, data) {
    let returnValue = null;
    $.ajax({
        url: url,
        type: type,
        async: false,
        data: data,
        dataType: 'json',
        success: function (resp) {
            returnValue = resp;
        }
    });
    return returnValue;
}

次に、関数を呼び出します。

let password = $("#password").val();
        let email = $("#email").val();
        let data = {
            email: email,
            password: password,
        };
        let  resp =  sendRequest('POST', 'http://localhost/signin')}}", data);
        console.log(resp);

コードの 重要な注意事項は次のとおりです。async: false

このソリューションが機能しない場合は、一部のブラウザまたはjQueryバージョンで機能しない可能性があることに注意してください。

于 2020-11-08T10:17:11.017 に答える
-4

Node.jsのXHRをasync-awaitに変換する簡単なコード例

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();

function xhrWrapWithPromise() {
  return new Promise((resolve, reject) => {
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4) {
        if (this.status == 200) {
          resolve(this.responseText);
        } else {
          reject(new Error("Couldn't feth data finally"));
        }
      }
    };
    xhttp.open("GET", "https://www.w3schools.com/xml/xmlhttp_info.txt", true);
    xhttp.send();
  });
}

// We need to wrap await in Async function so and anonymous IIFE here
(async _ => {
  try {
    let result = await xhrWrapWithPromise();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
})();
于 2018-12-02T12:41:39.607 に答える
-6

await常にPromiseを返すため、値を抽出するために(関数内で)追加を実行するだけですawaitasync

test(); // This alerts "hello"

// This is the outer function that wants to get the string result of inner()
async function test() {
  var str=await await inner();
  alert(str);
} // test

// This ia an inner function that can do arbitrary async operations
async function inner() {
  return Promise.resolve('hello');
}

于 2020-10-31T17:17:55.947 に答える