如何从发出异步请求的函数 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` }
如果您没有在代码中使用 jQuery,这个答案适合您
你的代码应该是这样的:
菲利克斯·克林做得很好工作为使用 jQuery for AJAX 的人编写答案,但我决定为不使用 jQuery 的人提供替代方案。
(注意,对于那些使用新的
fetch
API、Angular 或 Promise 的人,我添加了另一个答案如下)你面临的问题
这是另一个答案中“问题的解释”的简短摘要,如果您在阅读后不确定,请阅读该答案。
AJAX 中的 A 代表异步。这意味着发送请求(或者更确切地说接收响应)被从正常执行流程中删除。在您的示例中,
.send code>
立即返回,并且在调用您作为success
回调传递的函数之前执行下一条语句return result;
。这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。
这是一个简单的类比:
(小提琴)
由于
a=5
部分尚未执行,因此返回的a
值为undefined
。 AJAX 的行为是这样的,您在服务器有机会告诉您的浏览器该值是什么之前就返回了该值。此问题的一个可能的解决方案是重新主动编写代码,告诉您的程序在计算完成后要做什么。
这称为CPS。基本上,我们向
getFive
传递一个要在完成时执行的操作,我们告诉我们的代码如何在事件完成时做出反应(例如我们的 AJAX 调用,或者在本例中是超时)。用法是:
屏幕上会提示“5”。 (小提琴)。
可能的解决方案
解决这个问题基本上有两种方法:
1。同步 AJAX - 不要这样做!!
至于同步 AJAX,不要这样做!Felix 的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并造成非常糟糕的用户体验。以下是来自 MDN 的另一个简短总结,说明原因:
如果您不得不这样做,您可以传递一个标志。 具体方法如下:
2。重组代码
让您的函数接受回调。在示例代码中,可以使
foo
接受回调。我们将告诉代码当foo
完成时如何反应。所以:
变成:
这里我们传递了一个匿名函数,但我们也可以轻松传递对现有函数的引用,使其看起来像:
有关如何完成此类回调设计的更多详细信息,请查看 Felix 的回答。
现在,让我们定义 foo 本身以进行相应的操作
(小提琴)
现在,我们已经让 foo 函数接受一个操作,以便在 AJAX 成功完成时运行。我们可以通过检查响应状态是否不是 200 并采取相应措施(创建失败处理程序等)来进一步扩展此功能。它有效地解决了我们的问题。
如果您仍然很难理解这一点,阅读 AJAX 获取在 MDN 上开始指南。
问题
Ajax 中的 A 代表 异步。这意味着发送请求(或者更确切地说接收响应)被从正常执行流程中删除。在您的示例中,
$.ajax
立即返回,并且下一条语句return result;
在您作为success
回调传递的函数之前执行甚至打电话。这是一个类比,希望可以使同步流和异步流之间的区别更加清晰:
同步
想象一下,您给朋友打电话并请他为您查找一些信息。尽管可能需要一段时间,但您还是在电话旁等待,凝视着太空,直到您的朋友给您所需的答案。
当您进行包含“正常”代码的函数调用时,也会发生同样的情况:
尽管
findItem
可能需要很长时间才能执行,但var item = findItem();
之后的任何代码都必须等待直到该函数返回结果。异步
您出于同样的原因再次给您的朋友打电话。但这次你告诉他你很着急,他应该用你的手机给你回电。你挂断电话,离开家,做你计划做的事情。一旦您的朋友给您回电,您就正在处理他提供给您的信息。
这正是您发出 Ajax 请求时所发生的情况。
不等待响应,而是立即继续执行,并执行 Ajax 调用之后的语句。为了最终获得响应,您需要提供一个在收到响应后调用的函数,即回调(注意到什么了吗?回调?)。该调用之后的任何语句都会在调用回调之前执行。
解决方案
拥抱 JavaScript 的异步特性!虽然某些异步操作提供同步对应项(“Ajax”也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中。
你问为什么不好?
JavaScript 在浏览器的 UI 线程中运行,任何长时间运行的进程都会锁定 UI,使其无响应。另外,JavaScript的执行时间是有上限的,浏览器会询问用户是否继续执行。
所有这些都会导致非常糟糕的用户体验。用户将无法判断一切是否正常。此外,对于网速较慢的用户,效果会更差。
下面我们将介绍三种不同的解决方案,它们都是相互构建的:
async/await
的 Promise(ES2017+,如果您使用转译器或再生器,则可在旧版浏览器中使用)then() 的 Promise
(ES2015+,如果您使用众多 Promise 库之一,则可在旧版浏览器中使用)这三个功能均可在当前浏览器和 Node 7+ 中使用。
ES2017+:使用
async/await 进行承诺
2017 年发布的 ECMAScript 版本引入了对异步函数的语法级支持。借助
async
和await
,您可以以“同步风格”编写异步。代码仍然是异步的,但更容易阅读/理解。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
和await
。这是一个详细说明上面的延迟函数
findItem()
的示例:当前浏览器和node 版本支持
async/await
。您还可以借助 regenerator (或使用 regenerator 的工具)将代码转换为 ES5,以支持较旧的环境,例如 Babel)。让函数接受回调
回调是指函数 1 传递给函数 2 时。函数 2 可以在函数 1 准备好时调用它。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果会传递给回调。
在问题的示例中,您可以使
foo
接受回调并将其用作success
回调。所以这个变成了
这里我们定义了“内联”函数,但您可以传递任何函数引用:
foo
本身定义如下:callback
将引用我们调用时传递给foo
的函数,并将其传递给success
。 IE。一旦Ajax请求成功,$.ajax
将调用callback
并将响应传递给回调(可以用result
引用,因为这就是我们定义回调的方式)。您还可以在将响应传递给回调之前对其进行处理:
使用回调编写代码比看起来更容易。毕竟,浏览器中的 JavaScript 很大程度上是事件驱动的(DOM 事件)。接收 Ajax 响应只不过是一个事件。 当您必须使用第三方代码时可能会出现困难,但大多数问题只需思考应用程序流程就可以解决。
ES2015+:带有 then()的 Promise >
Promise API 是一个新的ECMAScript 6 (ES2015) 的功能,但它已经具有良好的浏览器支持。还有许多库实现了标准 Promises API 并提供了其他方法来简化异步函数的使用和组合(例如,蓝鸟)。
Promise 是未来值的容器。当 Promise 收到值(已解决)或被取消(拒绝)时,它会通知所有想要访问该值的“侦听器”。
与普通回调相比的优点是它们允许您解耦代码并且更容易编写。
这是使用 Promise 的示例: