搜索

首页  >  问答  >  正文

如何从异步调用返回响应?

<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粉668113768P粉668113768487 天前605

全部回复(2)我来回复

  • P粉334721359

    P粉3347213592023-08-24 11:47:12

    如果您没有在代码中使用 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粉642920522

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


    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
  • 取消回复