P粉3347213592023-08-24 11:47:12
你的程式碼應該是這樣的:
function foo() { var httpRequest = new XMLHttpRequest(); httpRequest.open('GET', "/echo/json"); httpRequest.send(); return httpRequest.responseText; } var result = foo(); // Always ends up being 'undefined'
菲利克斯·克林做得很好工作為使用 jQuery for AJAX 的人編寫答案,但我決定為不使用 jQuery 的人提供替代方案。
(注意,對於那些使用新的 fetch
API、Angular 或 Promise 的人,我添加了另一個答案如下)
這是另一個答案中「問題的解釋」的簡短摘要,如果您在閱讀後不確定,請閱讀該答案。
AJAX 中的 A 代表非同步。這意味著發送請求(或更確切地說接收回應)被從正常執行流程中刪除。在您的範例中, .send code>
立即返回,並且在呼叫您作為success
回呼傳遞的函數之前執行下一條語句return result;
。
這表示當您返回時,您定義的偵聽器尚未執行,這表示您傳回的值尚未定義。
這是一個簡單的類比:
function getFive(){ var a; setTimeout(function(){ a=5; },10); return a; }
#由於 a=5
部分尚未執行,因此傳回的 a
值為 undefined
。 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 調用,或在本例中是超時) 。 < /p>
用法是:
getFive(onComplete);
螢幕上會提示「5」。 (小提琴)。
解決這個問題基本上有兩種方法:
至於同步 AJAX,不要這樣做! Felix 的回答提出了一些令人信服的論點,說明為什麼這是一個壞主意。總而言之,它會凍結用戶的瀏覽器,直到伺服器回傳回應並造成非常糟糕的用戶體驗。以下是來自 MDN 的另一個簡短總結,說明原因:
如果您不得不這樣做,您可以傳遞一個標誌。 具體方法如下:
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); }
讓您的函數接受回呼。在範例程式碼中,可以使 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 並採取相應措施(建立失敗處理程序等)來進一步擴展此功能。它有效地解決了我們的問題。
如果您仍然很難理解這一點,閱讀 AJAX 取得在 MDN 上開始指南。
P粉6429205222023-08-24 00:16:16
Ajax 中的 A 代表 非同步。這意味著發送請求(或更確切地說接收回應)被從正常執行流程中刪除。在您的範例中,$.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 the item }); doSomethingElse();
不等待回應,而是立即繼續執行,並執行 Ajax 呼叫之後的語句。為了最終獲得回應,您需要提供一個在收到回應後呼叫的函數,即回調(注意到什麼了嗎?回調?)。該呼叫之後的任何語句都會在呼叫回調之前執行。
擁抱 JavaScript 的非同步特性! 雖然某些非同步操作提供同步對應項目(「Ajax」也是如此),但通常不鼓勵使用它們,尤其是在瀏覽器上下文中。
你問為什麼不好?
JavaScript 在瀏覽器的 UI 執行緒中執行,任何長時間運行的進程都會鎖定 UI,使其無回應。另外,JavaScript的執行時間是有上限的,瀏覽器會詢問使用者是否繼續執行。
所有這些都會導致非常糟糕的使用者體驗。用戶將無法判斷一切是否正常。此外,對於網路速度較慢的用戶,效果會更差。
下面我們將介紹三種不同的解決方案,它們都是相互建構的:
async/await
的 Promise(ES2017 ,如果您使用轉譯器或再生器,則可在舊版瀏覽器中使用)then() 的 Promise
(ES2015 ,如果您使用眾多 Promise 庫之一,則可在舊版瀏覽器中使用)這三個功能皆可在目前瀏覽器和 Node 7 中使用。
async/await 進行承諾
2017 年發布的 ECMAScript 版本引入了對非同步函數的語法級支援。使用async
和await
,您可以以「同步風格」編寫非同步。程式碼仍然是異步的,但更容易閱讀/理解。
async/await
建構在 Promise 之上:async
函數總是傳回 Promise。 await
“解開”一個 Promise,並且要么產生 Promise 被解析的值,要么在 Promise 被拒絕時拋出錯誤。
重要提示:您只能在async
函數或await .org/en -US/docs/Web/JavaScript/Guide/Modules" rel="noreferrer">JavaScript 模組。模組外部不支援頂層 await
,因此您可能必須建立非同步 IIFE (立即呼叫函數表達式)來啟動非同步
上下文(如果不使用模組)。
您可以閱讀有關async<的更多信息/code>
和 await< MDN 上的 /code>
。
這是一個詳細說明上面的延遲函數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 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); })();
目前瀏覽器和node 版本支援 async/await
。您也可以藉助 regenerator (或使用 regenerator 的工具)將程式碼轉換為 ES5,以支援較舊的環境,例如 Babel)。
回呼是指函數 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 }); }
callback
將引用我們呼叫時傳遞給 foo
的函數,並將其傳遞給 success
。 IE。一旦Ajax請求成功,$.ajax
將呼叫callback
並將回應傳遞給回呼(可以用result
引用,因為這就是我們定義回呼的方式) 。
您也可以在將回應傳遞給回調之前對其進行處理:
function foo(callback) { $.ajax({ // ... success: function(response) { // For example, filter the response callback(filtered_response); } }); }
使用回調編寫程式碼比看起來更容易。畢竟,瀏覽器中的 JavaScript 很大程度上是事件驅動的(DOM 事件)。接收 Ajax 回應只不過是一個事件。 當您必須使用第三方程式碼時可能會出現困難,但大多數問題只需思考應用程式流程即可解決。
#Promise API 是一個新的ECMAScript 6 (ES2015) 的功能,但它已經具有良好的瀏覽器支援。還有許多函式庫實作了標準 Promises API 並提供了其他方法來簡化非同步函數的使用和組合(例如,藍鳥)。
Promise 是未來值的容器。當 Promise 收到值(已解決)或被取消(拒絕)時,它會通知所有想要存取該值的「偵聽器」。 < /p>
與普通回調相比的優點是它們允許您解耦程式碼並且更容易編寫。
這是使用 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; }