await用法 - run async function javascript




如何從異步調用返迴響應? (20)

我有一個函數foo ,它發出Ajax請求。 我如何從foo返迴響應?

我嘗試從success回調中返回值,並將響應分配給函數內部的局部變量並返回該變量,但這些方法都沒有實際返迴響應。

function foo() {
    var result;

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

    return result;
}

var result = foo(); // It always ends up being `undefined`.

→有關不同示例的異步行為的更一般說明,請參閱 我在函數內部修改變量之後為什麼變量不變? - 異步代碼引用

→如果您已經了解問題,請跳至下面的可能解決方案。

問題

AjaxA代表asynchronous 。 這意味著發送請求(或者更確切地說是接收響應)將從正常執行流程中取出。 在您的示例中, $.ajax立即返回並返回下一個語句, return result; ,在您傳遞的函數之前執行,因為甚至調用了success回調。

這是一個類比,希望使同步和異步流之間的區別更加清晰:

同步

想像一下,你打電話給朋友,讓他為你尋找一些東西。 雖然可能需要一段時間,但你要等電話並凝視太空,直到你的朋友給你你需要的答案。

當您進行包含“普通”代碼的函數調用時,會發生同樣的情況:

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

var item = findItem();

// Do something with item
doSomethingElse();

儘管findItem可能需要很長時間才能執行,但任何代碼都會出現在var item = findItem(); 必須等到函數返回結果。

異步

你出於同樣的原因再次打電話給你的朋友。 但是這次你告訴他你很匆忙,他應該用手機給你回電話。 你掛斷了,離開了房子,做了你打算做的事情。 一旦你的朋友給你回電話,你正在處理他給你的信息。

這正是您執行Ajax請求時發生的情況。

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

而不是等待響應,執行立即繼續執行Ajax調用之後的語句。 為了最終得到響應,你提供了一個在收到響應後調用的函數,一個回調 (注意一些事情? 回調 ?)。 調用之後的任何語句都會在調用回調之前執行。

解決方案(S)

擁抱JavaScript的異步特性! 雖然某些異步操作提供了同步對應(“Ajax”也是如此),但通常不鼓勵使用它們,尤其是在瀏覽器上下文中。

你問為什麼這麼糟糕?

JavaScript在瀏覽器的UI線程中運行,任何長時間運行的進程都會鎖定UI,使其無響應。 此外,JavaScript的執行時間有一個上限,瀏覽器會詢問用戶是否繼續執行。

所有這些都是非常糟糕的用戶體驗。 用戶將無法判斷一切是否正常。 此外,對於連接速度慢的用戶,效果會更差。

在下文中,我們將看看三種不同的解決方案,它們都是相互建立的:

  • 使用async/await承諾 (ES2017 +,如果您使用轉換器或再生器,則可在舊版瀏覽器中使用)
  • 回調 (在節點中很流行)
  • 承諾與then() (ES2015 +,如果您使用眾多承諾庫之一,可在舊版瀏覽器中使用)

這三種都可以在當前瀏覽器和節點7+中使用。

ES2017 +:與async/await承諾

2017年發布的ECMAScript版本引入了異步函數的語法級支持 。 在asyncawait的幫助下,您可以在“同步樣式”中編寫異步。 代碼仍然是異步的,但它更容易閱讀/理解。

async/await建立在promises之上: async函數總是返回一個promise。 await “解包”一個承諾,或者導致承諾被解決的值,或者如果承諾被拒絕則拋出錯誤。

重要提示:您只能在async函數中使用await 。 這意味著,在最高級別,您仍然必須直接與承諾一起工作。

您可以在MDN上閱讀有關async/awaitawait更多信息。

這是一個建立在上面延遲之上的例子:

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

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


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

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

當前的browsernode版本支持async/await 。 您還可以通過在regenerator (或使用再生器的工具,如Babel )的幫助下將代碼轉換為ES5來支持舊環境。

讓函數接受回調

回調只是傳遞給另一個函數的函數。 其他函數可以在函數準備就緒時調用函數。 在異步過程的上下文中,只要異步過程完成,就會調用回調。 通常,結果將傳遞給回調。

在問題的示例中,您可以讓foo接受回調並將其用作success回調。 所以這

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

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

這裡我們定義了函數“inline”,但你可以傳遞任何函數引用:

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

foo(myCallback);

foo本身定義如下:

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

callback將引用我們調用它時傳遞給foo的函數,我們只是將其傳遞給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()

then()是ECMAScript 6(ES2015)的新功能,但它已經具有良好的瀏覽器支持 。 還有許多庫實現了標準的Promises API,並提供了其他方法來簡化異步函數(例如bluebird )的使用和組合。

承諾是未來價值觀的容器。 當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調用,我們可以使用這樣的promises:

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

描述承諾提供的所有優點超出了本答案的範圍,但如果您編寫新代碼,則應認真考慮它們。 它們提供了很好的抽象和代碼分離。

關於promises的更多信息: HTML5 rocks - JavaScript Promises

旁注:jQuery的延遲對象

延遲對像是jQuery的promises自定義實現(在Promise API標準化之前)。 它們的行為幾乎與承諾相似,但暴露出略微不同的API。

jQuery的每個Ajax方法都已經返回一個“延遲對象”(實際上是一個延遲對象的承諾),你可以從你的函數返回:

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

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

旁注:承諾陷阱

請記住,promises和deferred對像只是未來值的容器 ,它們本身並不是值。 例如,假設您有以下內容:

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

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

此代碼誤解了上述異步問題。 具體來說, $.ajax()在檢查服務器上的'/ password'頁面時不凍結代碼 - 它向服務器發送請求,當它等待時,立即返回一個jQuery Ajax Deferred對象,而不是來自服務器。 這意味著if語句將始終獲取此Deferred對象,將其視為true ,並繼續進行,就像用戶已登錄一樣。不好。

但修復很簡單:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

不推薦:同步“Ajax”調用

正如我所提到的,一些(!)異步操作具有同步對應物。 我並不主張使用它們,但為了完整起見,以下是執行同步調用的方法:

沒有jQuery

如果直接使用XMLHTTPRequest對象, .open false作為第三個參數傳遞給.open

jQuery的

如果使用jQuery ,則可以將async選項設置為false 。 請注意,自jQuery 1.8以來不推薦使用此選項。 然後,您可以仍然使用success回調或訪問jqXHR對象responseText屬性:

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

如果您使用任何其他jQuery Ajax方法,例如$.get$.getJSON等,則必須將其更改為$.ajax (因為您只能將配置參數傳遞給$.ajax )。

當心! 無法生成同步JSONP請求。 JSONP本質上總是異步的(甚至不考慮這個選項的另一個原因)。


Js是單線程的。

瀏覽器可以分為三個部分:

1)事件循環

2)Web API

3)事件隊列

Event Loop永遠運行,即一種無限循環.Event Queue是在某些事件上推送所有函數的地方(例如:click)這是逐個執行隊列並放入Event循環執行此函數並準備自己執行第一個函數後執行下一個函數。這意味著在事件循環中執行隊列中的函數之前,不會啟動一個函數的執行。

現在讓我們認為我們在隊列中推送了兩個函數,一個用於從服務器獲取數據,另一個用於利用該數據。我們先將隊列中的serverRequest()函數推送到utiliseData()函數。 serverRequest函數進入事件循環並調用服務器,因為我們永遠不知道從服務器獲取數據需要多長時間,因此這個過程需要花費時間,因此我們忙於事件循環從而掛起我們的頁面,那就是Web API發揮作用它從事件循環中獲取此函數並處理服務器使事件循環空閒,以便我們可以從隊列執行下一個函數。隊列中的下一個函數是utiliseData(),它循環但由於沒有數據可用它浪費和下一個函數的執行一直持續到隊列結束。(這稱為異步調用,即我們可以做其他事情,直到我們得到數據)

假設我們的serverRequest()函數在代碼中有一個return語句,當我們從服務器獲取數據時,Web API會在隊列末尾將其推送到隊列中。當它在隊列末尾被推送時我們無法利用它的數據,因為我們的隊列中沒有剩餘的功能來利用這些數據。因此無法從異步調用返回任何內容。

因此,對此的解決方案是回調承諾

來自其中一個答案的圖像,正確解釋回調使用...我們將功能(利用服務器返回的數據的功能)提供給功能調用服務器。

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

在我的代碼中,它被稱為

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

請閱讀此處了解ECMA(2016/17)中用於進行異步呼叫的新方法(@Felix Kling在頂部回答)https://.com/a/14220323/7579856


如果您沒有在代碼中使用jQuery,那麼這個答案就適合您

你的代碼應該是這樣的:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Felix Kling為使用jQuery for AJAX的人寫了一個很好的答案,我決定為那些沒有使用jQuery的人提供替代方案。

( 注意,對於那些使用新的fetch API,Angular或promises,我在下面添加了另一個答案 )

你面對的是什麼

這是另一個答案的“問題解釋”的簡短摘要,如果您在閱讀本文後不確定,請閱讀。

AJAX中的A代表異步 。 這意味著發送請求(或者更確切地說是接收響應)將從正常執行流程中取出。 在你的例子中, .send立即返回並返回下一個語句, return result; ,在您傳遞的函數之前執行,因為甚至調用了success回調。

這意味著當您返回時,您定義的偵聽器尚未執行,這意味著您返回的值尚未定義。

這是一個簡單的比喻

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

(Fiddle)

返回的值undefined因為a=5部分尚未執行。 AJAX就像這樣,你在服務器有機會告訴瀏覽器這個值是什麼之前返回值。

解決此問題的一種可能方法是重新編寫代碼,告訴程序在計算完成時該怎麼做。

function onComplete(a){ // When the code completes, do this
    alert(a);
}

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

這稱為CPS 。 基本上,我們正​​在通過getFive傳遞一個動作來完成它,我們告訴我們的代碼在事件完成時如何反應(比如我們的AJAX調用,或者在這種情況下是超時)。

用法是:

getFive(onComplete);

哪個應警告“5”到屏幕。 (Fiddle)

可能的解決方案

基本上有兩種解決方法:

  1. 使AJAX調用同步(讓我們稱之為SJAX)。
  2. 重構代碼以使用回調正常工作。

1.同步AJAX - 不要這樣做!!

至於同步AJAX, 不要這樣做! 菲利克斯的回答提出了一些令人信服的論據,說明為什麼這是一個壞主意。 總結一下,它會凍結用戶的瀏覽器,直到服務器返迴響應並創建非常糟糕的用戶體驗。 以下是MDN的另一個簡短摘要:

XMLHttpRequest支持同步和異步通信。 但是,一般而言,出於性能原因,異步請求應優先於同步請求。

簡而言之,同步請求會阻止代碼的執行......這可能會導致嚴重的問題......

如果你必須這樣做,你可以傳遞一個標誌: 這是如何:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2.重組代碼

讓你的函數接受回調。 在示例代碼中,可以使foo接受回調。 我們將告訴我們的代碼如何在foo完成時做出反應

所以:

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

變為:

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

這裡我們傳遞了一個匿名函數,但我們可以輕鬆地將引用傳遞給現有函數,使其看起來像:

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

有關如何完成此類回調設計的更多詳細信息,請查看Felix的答案。

現在,讓我們定義foo自己採取相應的行動

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

(fiddle)

我們現在已經讓我們的foo函數接受了一個在AJAX成功完成時運行的動作,我們可以通過檢查響應狀態是否為200並進行相應的操作來進一步擴展它(創建一個失敗處理程序等)。 有效解決我們的問題。

如果您仍然很難理解這一點,請閱讀 MDN上的AJAX入門指南


如果您使用承諾,這個答案適合您。

這意味著AngularJS,jQuery(帶有延遲),本機XHR的替換(fetch),EmberJS,BackboneJS的保存或任何返回promises的節點庫。

你的代碼應該是這樣的:

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為使用jQuery和AJAX回調的人寫了一個很好的答案。 我有一個原生XHR的答案。 這個答案是對前端或後端的承諾的一般用法。

核心問題

瀏覽器和NodeJS / io.js服務器上的JavaScript並發模型是異步被動的

每當你調用一個返回一個promise的方法時, then處理程序總是異步執行 - 也就是說, 它們下面的代碼不在.then處理程序之後。

這意味著當您返回data ,您定義的處理程序尚未執行。 這反過來意味著您返回的值未及時設置為正確的值。

以下是該問題的簡單類比:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

data的值undefined因為data = 5部分尚未執行。 它可能會在一秒鐘內執行,但到那時它與返回的值無關。

由於操作尚未發生(AJAX,服務器調用,IO,計時器),因此在請求有機會告訴您的代碼該值是什麼之前,您將返回該值。

解決此問題的一種可能方法是重新編寫代碼,告訴程序在計算完成時該怎麼做。 承諾通過本質上是暫時的(時間敏感的)來積極地實現這一點。

快速回顧承諾

承諾是一種隨時間變化價值 。 承諾有狀態,它們開始等待沒有價值,可以滿足:

  • 實現了計算成功完成的意義。
  • 拒絕意味著計算失敗。

承諾只能改變一次狀態之後它將永遠保持在同一狀態。 您可以將處理程序附加到promises以提取其值並處理錯誤。 then處理程序允許chaining電話。 Promise是通過使用返回它們的API創建的。 例如,更現代的AJAX替換fetch或jQuery的$.get返回promise。

當我們在承諾上調用。然後從承諾中返回一些東西時 - 我們得到了對已處理值的承諾。 如果我們回到另一個承諾,我們會得到驚人的東西,但讓我們抓住我們的馬。

有了承諾

讓我們看看我們如何用promises解決上述問題。 首先,讓我們通過使用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轉換為使用promises之後,我們可以使用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`
})

所以這也適用。我們已經知道我們不能從已經異步的調用中返回值,但我們可以使用promises並將它們鏈接起來執行處理。我們現在知道如何從異步調用返迴響應。

ES2015(ES6)

ES6引入了generators,這些generators可以在中間返回,然後恢復它們所處的位置。這通常對序列有用,例如:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

是一個函數,它返回可以迭代的序列1,2,3,3,3,3,....上的迭代器。雖然這本身很有趣並且為很多可能性開闢了空間,但有一個特別有趣的案例。

如果我們生成的序列是一系列動作而不是數字 - 我們可以在每次動作時暫停該函數並在我們恢復該函數之前等待它。因此,我們需要一系列未來值 - 而不是一系列數字- 即:承諾。

這個有點棘手但非常強大的技巧讓我們以同步的方式編寫異步代碼。有幾個“跑步者”為你做這個,寫一個是幾行代碼,但超出了這個答案的範圍。我將在Promise.coroutine這裡使用Bluebird ,但還有其他包裝,如coQ.async

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

此方法返回一個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保證。通過添加asyncawait關鍵字,這只是上面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
}

它仍然返回一個相同的承諾:)


以下是一些使用異步請求的方法:

  1. then()
  2. Q - JavaScript的承諾庫
  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();


XMLHttpRequest 2 (首先閱讀Benjamin Gruenbaum和Felix Kling的答案)

如果你不使用jQuery並想要一個很好的簡短的XMLHttpRequest 2,它可以在現代瀏覽器和移動瀏覽器上運行,我建議用這種方式:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

如你看到的:

  1. 它比列出的所有其他功能短。
  2. 回調是直接設置的(因此沒有額外的不必要的閉包)。
  3. 它使用新的onload(因此您不必檢查readystate && status)
  4. 還有一些我不記得的情況使得XMLHttpRequest 1令人討厭。

有兩種方法可以獲得此Ajax調用的響應(三種使用XMLHttpRequest var名稱):

最簡單的:

this.response

或者,如果由於某種原因你bind()回調bind()到一個類:

e.target.response

例:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

或者(上面的一個是更好的匿名函數總是一個問題):

ajax('URL', function(e){console.log(this.response)});

沒什麼比這更容易

現在有些人可能會說最好使用onreadystatechange或甚至XMLHttpRequest變量名。 那是錯的。

查看XMLHttpRequest高級功能

它支持所有*現代瀏覽器。 我可以確認,因為我使用這種方法,因為XMLHttpRequest 2存在。 在我使用的所有瀏覽器上,我從未遇到任何類型的問題。

onreadystatechange僅在您希望獲取狀態2的標頭時才有用。

使用XMLHttpRequest變量名是另一個重大錯誤,因為您需要在onload / oreadystatechange閉包內執行回調,否則您將丟失它。

現在,如果你想使用post和FormData更複雜的東西,你可以輕鬆擴展這個功能:

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

再次......這是一個非常短的功能,但它確實得到和發布。

用法示例:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

或者傳遞一個完整的表單元素( document.getElementsByTagName('form')[0] ):

var fd = new FormData(form);
x(url, callback, 'post', fd);

或者設置一些自定義值:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

如你所見,我沒有實現同步...這是一件壞事。

話雖如此......為什麼不這麼簡單呢?

正如評論中所提到的,使用error && synchronous確實完全打破了答案的要點。 哪個是以正確的方式使用Ajax的簡短方法?

錯誤處理程序

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

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

在上面的腳本中,您有一個靜態定義的錯誤處理程序,因此它不會破壞該功能。 錯誤處理程序也可用於其他功能。

但要真正解決錯誤, 唯一的方法是寫一個錯誤的URL,在這種情況下每個瀏覽器都會拋出一個錯誤。

如果您設置自定義標頭,將responseType設置為blob數組緩衝區或其他任何內容,錯誤處理程序可能很有用....

即使你傳遞'POSTAPAPAP'作為方法它也不會拋出錯誤。

即使你將'fdggdgilfdghfldj'作為formdata傳遞它也不會拋出錯誤。

在第一種情況下,錯誤在this.statusText下的displayAjax()內,因為Method not Allowed

在第二種情況下,它只是起作用。 如果您傳遞了正確的帖子數據,則必須在服務器端進行檢查。

跨域不允許自動拋出錯誤。

在錯誤響應中,沒有錯誤代碼。

只有this.type設置為error。

如果您完全無法控制錯誤,為什麼要添加錯誤處理程序? 大多數錯誤都在回調函數displayAjax()

因此:如果您能夠正確複製和粘貼URL,則無需進行錯誤檢查。 ;)

PS:作為我寫的第一個測試x('x',displayAjax)......,它完全得到了回复...... ??? 所以我檢查了HTML所在的文件夾,並且有一個名為'x.xml'的文件。 因此,即使您忘記了文件的擴展名,XMLHttpRequest 2也會找到它 。 我好意思

同步讀取文件

不要那樣做。

如果你想阻止瀏覽器一段時間加載一個漂亮的大txt文件同步。

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

現在你可以做到

 var res = omg('thisIsGonnaBlockThePage.txt');

沒有其他方法可以以非異步方式執行此操作。 (是的,使用setTimeout循環......但是認真嗎?)

另一點是......如果您使用API​​或只是您擁有列表的文件或任何您總是為每個請求使用不同的函數...

只有當你有一個頁面,你總是加載相同的XML / JSON或任何你只需要一個函數。 在這種情況下,修改一點Ajax函數並用您的特殊函數替換b。

以上功能僅供基本使用。

如果你想擴展功能......

是的你可以。

我使用了很多API,我在每個HTML頁面中集成的第一個函數之一是這個答案中的第一個Ajax函數,只有GET ...

但是你可以用XMLHttpRequest 2做很多事情:

我製作了一個下載管理器(使用簡歷,文件讀取器,文件系統兩側的範圍),使用畫布的各種圖像縮放器轉換器,使用base64images填充websql數據庫等等......但在這些情況下,您應該僅為此目的創建一個函數...有時你需要一個blob,數組緩衝區,你可以設置標題,覆蓋mimetype,還有更多......

但這裡的問題是如何返回Ajax響應...(我添加了一個簡單的方法。)


看看這個例子:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

正如您所看到的那樣getJoke返回已解決的承諾(在返回時會解決res.data.value)。所以你要等到$ http.get請求完成後再執行console.log(res.joke)(作為普通的異步流程)。

這是plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/


簡短回答:您的foo()方法立即返回,而在函數返回後$ajax()調用異步執行。問題是,一旦它返回,存儲異步調用檢索的結果的方式或位置。

該線程中給出了幾種解決方案。也許最簡單的方法是將對像傳遞給foo()方法,並在異步調用完成後將結果存儲在該對象的成員中。

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

請注意,調用foo()仍將返回任何有用的內容。但是,現在將存儲異步調用的結果result.response


使用ES2017,你應該將它作為函數聲明

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

像這樣執行它。

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

或Promise語法

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

問題是:

如何從異步調用返迴響應?

可以解釋為:

如何使異步代碼看起來同步

解決方案是避免回調,並使用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")

您可以使用此自定義庫(使用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)
});

我寫的以下示例說明瞭如何操作

  • 處理異步HTTP調用;
  • 等待每個API調用的響應;
  • 使用Promise模式;
  • 使用Promise.All模式加入多個HTTP調用;

這個工作示例是獨立的。它將定義一個使用window XMLHttpRequest對象進行調用的簡單請求對象。它將定義一個簡單的函數來等待一堆承諾完成。

語境。該示例是查詢Spotify Web API端點,以便搜索playlist給定查詢字符串集的對象:

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

對於每個項目,新的Promise將觸發一個塊 - ExecutionBlock解析結果,根據結果數組計劃一組新的promises,即Spotify user對象列表,並在ExecutionProfileBlock異步中執行新的HTTP調用。

然後,您可以看到嵌套的Promise結構,該結構允許您生成多個完全異步的嵌套HTTP調用,並加入來自每個調用子集的結果Promise.all

注意最近的Spotify searchAPI將要求在請求標頭中指定訪問令牌:

-H "Authorization: Bearer {your access token}" 

因此,您需要運行以下示例,您需要將訪問令牌放在請求標頭中:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

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

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

here廣泛討論了這個解決方案。


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

另一種解決方案是通過順序執行器nsynjs執行代碼。

如果潛在的功能被宣傳

nsynjs將按順序評估所有promise,並將promise結果放入dataproperty:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

如果潛在的功能沒有被宣傳

步驟1.使用回調函數將函數包裝到nsynjs-aware包裝器中(如果它具有promisified版本,則可以跳過此測試):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

步驟2.將同步邏輯放入功能:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

步驟3.通過nnsynjs以同步方式運行函數:

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

Nsynjs將逐步評估所有運算符和表達式,暫停執行,以防某些慢速函數的結果未準備就緒。

更多示例:https://github.com/amaksr/nsynjs/tree/master/exampleshttps://github.com/amaksr/nsynjs/tree/master/examples


您正在使用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);
    }
});

在提交處理程序中返回任何內容都不會執行任何操作。您必須切換數據,或者直接在成功函數內執行您想要的操作。


我們發現自己處於一個宇宙中,似乎沿著我們稱之為“時間”的維度前進。我們真的不明白什麼時候,但我們已經開發出抽象和詞彙,讓我們推理和討論它:“過去”,“現在”,“未來”,“之前”,“之後”。

我們構建的計算機系統 - 越來越多 - 將時間作為一個重要方面。某些事情將在未來發生。然後在最初發生的第一件事之後需要發生其他事情。這是稱為“異步性”的基本概念。在我們日益網絡化的世界中,最常見的異步性情況是等待某個遠程系統響應某些請求。

考慮一個例子。你打電話給送奶工並點一些牛奶。當它到來時,你想把它放在你的咖啡裡。你現在不能把牛奶放進你的咖啡裡,因為它還沒有。在將它放入咖啡之前,你必須等待它。換句話說,以下內容不起作用:

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; 相反,函數必須通過調用作為參數給出的回調來報告其結果。而且,當處理較長的事件序列時,這種方法會迅速變得難以處理。例如,假設我想等待將牛奶放入咖啡中,然後再進行第三步,即喝咖啡。我最終需要寫這樣的東西:

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

put_in_coffee將牛奶放入其中,以及drink_coffee放入牛奶後執行的action()。這樣的代碼變得難以編寫,讀取和調試。

在這種情況下,我們可以將問題中的代碼重寫為:

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)

這樣做的一個優點是我們可以將它們串在一起以創建未來發生的序列(“鏈接”):

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

讓我們對您的特定問題應用承諾。我們將請求邏輯包裝在一個函數中,該函數返回一個promise:

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

實際上,我們所做的就是添加一個return來調用$.ajax。這是有效的,因為jQuery $.ajax已經返回了一種類似承諾的東西。(在實踐中,沒有詳細介紹,我們寧願包裝這個調用,以便返回一個真正的承諾,或者使用一些替代方法$.ajax。)現在,如果我們想加載文件並等待它完成然後做點什麼,我們可以簡單地說

get_data() . then(do_something)

例如,

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

使用promises時,我們最終會傳遞大量函數then,因此使用更緊湊的ES6樣式箭頭函數通常很有幫助:

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

async關鍵字

但是,如果是同步的,必須以一種方式編寫代碼,如果是異步的,那麼仍然會有一種模糊的不滿。對於同步,我們寫

a();
b();

但如果a是異步的,我們必須寫下承諾

a() . then(b);

上面,我們說“JS無法知道它需要等待第一次調用才能執行第二次”。那豈不是很好,如果有一些方法來告訴JS呢?事實證明,存在 - await關鍵字,在稱為“異步”函數的特殊類型的函數中使用。此功能是即將推出的ES版本的一部分,但已經在諸如Babel之類的轉發器中提供了正確的預設。這讓我們可以簡單地寫

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

在你的情況下,你可以寫出類似的東西

async function foo() {
  data = await get_data();
  console.log(data);
}

最簡單的解決方案是創建一個JavaScript函數並調用它來進行Ajax success回調。

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

當然有許多方法,如同步請求,承諾,但根據我的經驗,我認為你應該使用回調方法。Javascript的異步行為很自然。因此,您的代碼段可以重寫一點:

function foo() {
    var result;

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

    return result;
}

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

而不是向你拋出代碼,有兩個概念是理解JS如何處理回調和異步性的關鍵。(那是一個字嗎?)

事件循環和並發模型

你需要注意三件事; 隊列; 事件循環和堆棧

在廣泛,簡單的術語中,事件循環就像項目管理器一樣,它不斷地監聽任何想要在隊列和堆棧之間運行和通信的函數。

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

一旦收到運行某事的消息,它就會將其添加到隊列中。隊列是等待執行的事物列表(如您的AJAX請求)。想像它是這樣的:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

當其中一條消息要執行時,它會彈出隊列中的消息並創建一個堆棧,堆棧是JS需要執行的所有操作來執行消息中的指令。因此,在我們的示例中,它被告知要打電話foobarFunc

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

所以foobarFunc需要執行的任何東西(在我們的例子中anotherFunction)都會被壓入堆棧。執行,然後忘記 - 事件循環將移動到隊列中的下一個事物(或偵聽消息)

這裡的關鍵是執行順序。 那是

什麼時候會發生

當您使用AJAX向外部方進行調用或運行任何異步代碼(例如setTimeout)時,Javascript依賴於響應,然後才能繼續。

最大的問題是什麼時候能得到答复?答案是我們不知道 - 因此事件循環正在等待該消息說“嘿運行我”。如果JS只是同步等待該消息,你的應用程序會凍結,它會很糟糕。因此,JS繼續執行隊列中的下一個項目,同時等待消息被添加回隊列。

這就是為什麼使用異步功能我們使用稱為回調的東西。它實際上就像一個then()。正如我承諾在某些時候返回一些東西, jQuery使用調用的特定回調deffered.done deffered.faildeffered.always(以及其他)。你可以here看到它們here

所以你需要做的是傳遞一個承諾在某個時刻執行的函數,並傳遞給它的數據。

因為回調不是立即執行的,而是在以後執行,所以將引用傳遞給函數並不重要。 所以

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

所以大部分的時間(但不總是),你會通過foofoo()

希望這會有所幫助。當你遇到這樣的事情似乎令人困惑時 - 我強烈建議你完全閱讀文檔,至少要了解它。它會讓你成為一個更好的開發者。


這是在許多新的JavaScript框架中使用的兩種數據綁定方式對你有用的地方之一......

因此,如果您使用Angular,React或任何其他兩種方式進行數據綁定,那麼這個問題只是為您修復,所以簡單來說,您的結果是undefined在第一階段,所以result = undefined在收到數據之前就已經得到了,然後,只要你得到結果,它就會更新並被分配給新的值,這是響應你的Ajax調用...

但是你如何在純javascriptjQuery中做到這一點,例如你在這個問題中提到的?

您可以使用回調函數,promise和最近的observable來為您處理它,例如在promise中我們有一些函數,如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
});

有關promisesobservables的更多信息,這些是更新的方法來執行此異步操作。





ecmascript-2017