首頁  >  問答  >  主體

如何從非同步呼叫中返回回應?

如何從發出非同步請求的函數 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`
}


#
P粉722409996P粉722409996376 天前591

全部回覆(2)我來回復

  • P粉477369269

    P粉4773692692023-10-09 09:40:11

    如果您沒有在程式碼中使用 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 立即返回,並且在呼叫您作為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」。 (小提琴)

    可能的解決方案

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

    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 上開始指南

    回覆
    0
  • P粉515066518

    P粉5150665182023-10-09 00:20:22

    問題

    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 (立即呼叫函數表達式)來啟動非同步上下文(如果不使用模組)。

    您可以閱讀有關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 回應只不過是一個事件。 當您必須使用第三方程式碼時可能會出現困難,但大多數問題只需思考應用程式流程即可解決。


    ES2015 :帶有 then()

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

    回覆
    0
  • 取消回覆