Wie kann ich eine Antwort/ein Ergebnis von einer Funktionfoo
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 gebenundefined
或其他变量result
mit dem Anfangswert zurück.
Beispiel einer asynchronen Funktion, die einen Rückruf akzeptiert(unter Verwendung der Funktionajax
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 desthen
-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` }
如果您没有在代码中使用 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
函数或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 的示例: