suchen

Heim  >  Fragen und Antworten  >  Hauptteil

Wie kann ich eine Antwort von einem asynchronen Aufruf zurückgeben?

Wie kann ich eine Antwort/ein Ergebnis von einer Funktion foo zurückgeben, die eine asynchrone Anfrage stellt?

Ich habe versucht, den Wert aus dem Rückruf zurückzugeben und das Ergebnis einer lokalen Variablen innerhalb der Funktion zuzuweisen und diese Variable zurückzugeben, aber keine dieser Methoden gibt tatsächlich eine Antwort zurück – sie alle geben undefined 或其他变量 result mit dem Anfangswert zurück.

Beispiel einer asynchronen Funktion, die einen Rückruf akzeptiert (unter Verwendung der Funktion ajax von jQuery):

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`
}

Beispiel mit 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`
}

Beispiel für die Verwendung des then-Versprechensblocks:

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粉722409996418 Tage vor625

Antworte allen(2)Ich werde antworten

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

    Antwort
    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 函数或 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; }

    Antwort
    0
  • StornierenAntwort