如何从异步调用返回响应?
P粉668113768
P粉668113768 2023-08-23 12:49:32
0
2
599
<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; }
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板