如何從非同步呼叫回傳回應?
P粉668113768
P粉668113768 2023-08-23 12:49:32
0
2
567
<p>如何從發出非同步請求的函數 <code>foo</code> 回傳回應/結果? </p> <p>我試圖從回調中返回值,並將結果分配給函數內的局部變量並返回該變量,但這些方法都沒有實際返回響應- 它們都返回<code>undefined< /code> 或變量<code>result</code> 的初始值。 </code></p><code> <p><strong>接受回呼的非同步函數範例</strong>(使用 jQuery 的 <code>ajax</code> 函數):</p> <pre class="brush:php;toolbar:false;">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` }</pre> <p><strong>使用 Node.js 的範例:</strong></p> <pre class="brush:php;toolbar:false;">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` }</pre> <p><strong>使用 Promise 的 <code>then</code> 區塊的範例:</strong></p> <pre class="brush:php;toolbar:false;">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` }</pre> <p><br /></p></code>
P粉668113768
P粉668113768

全部回覆(2)
P粉334721359

如果您沒有在程式碼中使用 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'

菲利克斯·克林做得很好工作為使用 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 調用,或在本例中是超時) 。

用法是:

getFive(onComplete);

螢幕上會提示「5」。 (小提琴)

可能的解決方案

解決這個問題基本上有兩種方法:

  1. 使 AJAX 呼叫同步(我們稱之為 SJAX)。
  2. 重構您的程式碼,以便與回呼一起正常工作。

1。同步 AJAX - 不要這麼做! !

至於同步 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);
}

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 並採取相應措施(建立失敗處理程序等)來進一步擴展此功能。它有效地解決了我們的問題。

如果您仍然很難理解這一點,閱讀 AJAX 取得在 MDN 上開始指南

P粉642920522

問題

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 中使用。


ES2017 :使用 async/await 進行承諾

2017 年發布的 ECMAScript 版本引入了對非同步函數的語法級支援。使用asyncawait,您可以以「同步風格」編寫非同步。程式碼仍然是異步的,但更容易閱讀/理解。

async/await 建構在 Promise 之上:async 函數總是傳回 Promise。 await “解開”一個 Promise,並且要么產生 Promise 被解析的值,要么在 Promise 被拒絕時拋出錯誤。

重要提示:您只能在async 函數或await .org/en -US/docs/Web/JavaScript/Guide/Modules" rel="noreferrer">JavaScript 模組。模組外部不支援頂層 await,因此您可能必須建立非同步 IIFE (立即呼叫函數表達式)來啟動非同步上下文(如果不使用模組)。

您可以閱讀有關asyncawait

這是一個詳細說明上面的延遲函數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 回應只不過是一個事件。 當您必須使用第三方程式碼時可能會出現困難,但大多數問題只需思考應用程式流程即可解決。


ES2015 :帶有 then()的 Promise >

#Promise API 是一個新的ECMAScript 6 (ES2015) 的功能,但它已經具有良好的瀏覽器支援。還有許多函式庫實作了標準 Promises API 並提供了其他方法來簡化非同步函數的使用和組合(例如,藍鳥)。

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; }
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板