P粉3347213592023-08-24 11:47:12
你的代码应该是这样的:
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 调用,或者在本例中是超时)。< /p>
用法是:
getFive(onComplete);
屏幕上会提示“5”。 (小提琴)。
解决这个问题基本上有两种方法:
至于同步 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); }
让您的函数接受回调。在示例代码中,可以使 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粉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+ 中使用。
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<的更多信息/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 响应只不过是一个事件。 当您必须使用第三方代码时可能会出现困难,但大多数问题只需思考应用程序流程就可以解决。
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; }