如何從發出非同步請求的函數 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` }
如果您沒有在程式碼中使用 jQuery,這個答案適合您
你的程式碼應該是這樣的:
菲利克斯·克林做得很好工作為使用 jQuery for AJAX 的人編寫答案,但我決定為不使用 jQuery 的人提供替代方案。
(注意,對於那些使用新的
fetch
API、Angular 或 Promise 的人,我添加了另一個答案如下)你面臨的問題
這是另一個答案中「問題的解釋」的簡短摘要,如果您在閱讀後不確定,請閱讀該答案。
AJAX 中的 A 代表非同步。這意味著發送請求(或更確切地說接收回應)被從正常執行流程中刪除。在您的範例中,
.send code>
立即返回,並且在呼叫您作為success
回呼傳遞的函數之前執行下一條語句return result;
。這表示當您返回時,您定義的偵聽器尚未執行,這表示您傳回的值尚未定義。
這是一個簡單的類比:
(小提琴)
#由於
a=5
部分尚未執行,因此傳回的a
值為undefined
。 AJAX 的行為是這樣的,您在伺服器有機會告訴您的瀏覽器該值是什麼之前就回傳了該值。此問題的一個可能的解決方案是重新主動編寫程式碼,告訴您的程式在計算完成後要做什麼。
這稱為CPS。基本上,我們向
getFive
傳遞一個要在完成時執行的操作,我們告訴我們的程式碼如何在事件完成時做出反應(例如我們的AJAX 調用,或在本例中是超時) 。用法是:
螢幕上會提示「5」。 (小提琴)。
可能的解決方案
解決這個問題基本上有兩種方法:
1。同步 AJAX - 不要這麼做! !
至於同步 AJAX,不要這樣做! Felix 的回答提出了一些令人信服的論點,說明為什麼這是一個壞主意。總而言之,它會凍結用戶的瀏覽器,直到伺服器回傳回應並造成非常糟糕的用戶體驗。以下是來自 MDN 的另一個簡短總結,說明原因:
如果您不得不這樣做,您可以傳遞一個標誌。 具體方法如下:
2。重組程式碼
讓您的函數接受回呼。在範例程式碼中,可以使
foo
接受回呼。我們將告訴程式碼當foo
完成時如何反應。所以:
變成:
這裡我們傳遞了一個匿名函數,但我們也可以輕鬆傳遞對現有函數的引用,使其看起來像:
有關如何完成此類回調設計的更多詳細信息,請查看 Felix 的回答。
現在,讓我們定義 foo 本身以進行對應的操作
(小提琴)
#現在,我們已經讓 foo 函數接受一個操作,以便在 AJAX 成功完成時運行。我們可以透過檢查回應狀態是否不是 200 並採取相應措施(建立失敗處理程序等)來進一步擴展此功能。它有效地解決了我們的問題。
如果您仍然很難理解這一點,閱讀 AJAX 取得在 MDN 上開始指南。
問題
Ajax 中的 A 代表 非同步。這意味著發送請求(或更確切地說接收回應)被從正常執行流程中刪除。在您的範例中,
$.ajax
立即返回,並且下一條語句return result;
在您作為success
回調傳遞的函數之前執行甚至打電話。這是一個類比,希望可以讓同步流和非同步流之間的差異更加清晰:
同步
想像一下,您打電話給朋友並請他為您查找一些資訊。儘管可能需要一段時間,但您還是在電話旁等待,凝視著太空,直到您的朋友給您所需的答案。
當您進行包含「正常」程式碼的函數呼叫時,也會發生相同的情況:
儘管
findItem
可能需要很長時間才能執行,但var item = findItem();
之後的任何程式碼都必須等待直到該函數返回結果。非同步
您出於同樣的原因再次打電話給您的朋友。但這次你告訴他你很著急,他應該用你的手機回電給你。你掛斷電話,離開家,做你計畫要做的事情。一旦您的朋友回電給您,您就正在處理他提供給您的資訊。
這正是您發出 Ajax 請求時所發生的情況。
不等待回應,而是立即繼續執行,並執行 Ajax 呼叫之後的語句。為了最終獲得回應,您需要提供一個在收到回應後呼叫的函數,即回調(注意到什麼了嗎?回調?)。該呼叫之後的任何語句都會在呼叫回調之前執行。
解決方案
擁抱 JavaScript 的非同步特性! 雖然某些非同步操作提供同步對應項目(「Ajax」也是如此),但通常不鼓勵使用它們,尤其是在瀏覽器上下文中。
你問為什麼不好?
JavaScript 在瀏覽器的 UI 執行緒中執行,任何長時間運行的進程都會鎖定 UI,使其無回應。另外,JavaScript的執行時間是有上限的,瀏覽器會詢問使用者是否繼續執行。
所有這些都會導致非常糟糕的使用者體驗。用戶將無法判斷一切是否正常。此外,對於網路速度較慢的用戶,效果會更差。
下面我們將介紹三種不同的解決方案,它們都是相互建構的:
async/await
的 Promise(ES2017 ,如果您使用轉譯器或再生器,則可在舊版瀏覽器中使用)then() 的 Promise
(ES2015 ,如果您使用眾多 Promise 庫之一,則可在舊版瀏覽器中使用)這三個功能皆可在目前瀏覽器和 Node 7 中使用。
ES2017 :使用
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
和await
。這是一個詳細說明上面的延遲函數
findItem()
的範例:目前瀏覽器和node 版本支援
async/await
。您也可以藉助 regenerator (或使用 regenerator 的工具)將程式碼轉換為 ES5,以支援較舊的環境,例如 Babel)。讓函數接受回呼
回呼是指函數 1 傳遞給函數 2 時。函數 2 可以在函數 1 準備好時呼叫它。在非同步進程的上下文中,只要非同步進程完成,就會呼叫回呼。通常,結果會傳遞給回調。
在問題的範例中,您可以使
foo
接受回呼並將其用作success
回呼。所以這個變成了
這裡我們定義了「內聯」函數,但您可以傳遞任何函數參考:
foo
本身定義如下:callback
將引用我們呼叫時傳遞給foo
的函數,並將其傳遞給success
。 IE。一旦Ajax請求成功,$.ajax
將呼叫callback
並將回應傳遞給回呼(可以用result
引用,因為這就是我們定義回呼的方式) 。您也可以在將回應傳遞給回調之前對其進行處理:
使用回調編寫程式碼比看起來更容易。畢竟,瀏覽器中的 JavaScript 很大程度上是事件驅動的(DOM 事件)。接收 Ajax 回應只不過是一個事件。 當您必須使用第三方程式碼時可能會出現困難,但大多數問題只需思考應用程式流程即可解決。
ES2015 :帶有 then()的 Promise >
#Promise API 是一個新的ECMAScript 6 (ES2015) 的功能,但它已經具有良好的瀏覽器支援。還有許多函式庫實作了標準 Promises API 並提供了其他方法來簡化非同步函數的使用和組合(例如,藍鳥)。
Promise 是未來值的容器。當 Promise 收到值(已解決)或被取消(拒絕)時,它會通知所有想要存取該值的「偵聽器」。
與普通回調相比的優點是它們允許您解耦程式碼並且更容易編寫。
這是使用 Promise 的範例: