.jpg)
网友问题:
如何从发出异步请求的函数返回响应/结果?foo
我正在尝试从回调中返回值,并将结果分配给函数内的局部变量并返回该变量,但这些方法实际上都没有返回响应——它们都返回或任何变量的初始值。undefinedresult
接受回调的异步函数示例(使用 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`
}
使用节点.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`
}

网友回答:
你的代码应该是这样的:
function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // Always ends up being 'undefined'
Felix Kling为使用jQuery for AJAX的人写了一个很好的答案,但我决定为那些不这样做的人提供一个替代方案。
(注意,对于那些使用新API,Angular或承诺的人,我在下面添加了另一个答案)fetch
这是另一个答案中“问题解释”的简短摘要,如果您在阅读本文后不确定,请阅读该答案。
AJAX 中的 A 代表异步。这意味着发送请求(或者更确切地说是接收响应)被排除在正常执行流之外。在您的示例中,立即返回,下一个语句 , 在您作为回调传递的函数甚至被调用之前执行。.sendreturn result;success
这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。
这里有一个简单的类比:
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
(小提琴)
返回的值是因为部件尚未执行。AJAX 的行为是这样的,在服务器有机会告诉您的浏览器该值是什么之前,您返回该值。aundefineda=5
这个问题的一个可能的解决方案是 反应式编码 ,告诉你的程序在计算完成后该做什么。
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
这称为CPS。基本上,我们正在传递一个在完成时要执行的操作,我们告诉我们的代码如何在事件完成时做出反应(例如我们的 AJAX 调用,或者在本例中为超时)。getFive
用法将是:
getFive(onComplete);
哪个应该向屏幕发出“5”警报。(小提琴)。
基本上有两种方法可以解决这个问题:
至于同步 AJAX,不要这样做!菲利克斯的回答提出了一些令人信服的论据,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。以下是MDN的另一个简短总结:
XMLHttpRequest 支持同步和异步通信。但是,出于性能原因,通常应首选异步请求而不是同步请求。
简而言之,同步请求会阻止代码的执行…这可能会导致严重的问题…
如果你必须这样做,你可以传递一个标志。方法如下:
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);
}
让您的函数接受回调。在示例中,可以使代码接受回调。我们将告诉我们的代码在完成后如何做出反应。
foofoo所以:
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 并采取相应的行动(创建失败处理程序等)来进一步扩展它。实际上,它正在解决我们的问题。
如果您仍然难以理解这一点,请阅读 MDN 的 AJAX 入门指南。

网友回答:
→ 有关不同示例的异步行为的更一般说明,请参阅为什么在函数内部修改变量后,我的变量保持不变?– 异步代码参考
→ 如果您已经了解问题,请跳到以下可能的解决方案。
Ajax 中的 A 代表异步。这意味着发送请求(或者更确切地说是接收响应)被排除在正常执行流之外。在您的示例中,立即返回,下一个语句 , 在您作为回调传递的函数甚至被调用之前执行。$.ajaxreturn result;success
这是一个类比,希望能更清楚地区分同步流和异步流:
想象一下,你给一个朋友打个电话,让他帮你查点什么。虽然这可能需要一段时间,但你还是等着电话,盯着太空,直到你的朋友给你你需要的答案。
当您进行包含“正常”代码的函数调用时,也会发生同样的情况:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
尽管可能需要很长时间才能执行,但之后的任何代码都必须等到函数返回结果。findItemvar item = findItem();
出于同样的原因,您再次打电话给您的朋友。但是这次你告诉他你很着急,他应该用你的手机给你回电话。你挂断电话,离开家,做你打算做的事情。一旦你的朋友给你回电话,你就在处理他给你的信息。
这正是执行 Ajax 请求时发生的情况。
findItem(function(item) {
// Do something with the item
});
doSomethingElse();
而不是等待响应,执行立即继续,并执行 Ajax 调用后的语句。为了最终获得响应,您需要提供一个在收到响应后调用的函数,即回调(注意到什么了吗?回电?该调用之后的任何语句都会在调用回调之前执行。
拥抱 JavaScript 的异步性质!虽然某些异步操作提供同步对应项(“Ajax”也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中。
你问为什么不好?
JavaScript 在浏览器的 UI 线程中运行,任何长时间运行的进程都将锁定 UI,使其无响应。此外,JavaScript 的执行时间有上限,浏览器会询问用户是否继续执行。
所有这些都导致了非常糟糕的用户体验。用户将无法判断一切是否正常工作。此外,对于连接速度较慢的用户,效果会更糟。
在下文中,我们将介绍三种不同的解决方案,它们都是相互构建的:
async/await 的承诺(ES2017+,如果您使用转译器或再生器,则在旧版浏览器中可用)then()的Promise(ES2015+,如果您使用众多Promise库之一,则可以在较旧的浏览器中使用)这三者在当前浏览器和节点 7+ 中都可用。
async/await2017 年发布的 ECMAScript 版本引入了对异步函数的语法级支持。借助 和 ,您可以以“同步样式”编写异步代码。代码仍然是异步的,但更容易阅读/理解。asyncawait
async/await建立在 promise 之上:函数总是返回一个 promise。 “解包”承诺,并产生解决承诺时使用的值,或者在承诺被拒绝时引发错误。asyncawait
重要:您只能在函数内部或 JavaScript 模块中使用。在模块之外不支持顶级,因此如果不使用模块,则可能必须创建异步 IIFE(立即调用的函数表达式)来启动上下文。awaitasyncawaitasync
您可以在MDN上阅读更多关于和的内容。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);
})();
当前浏览器和节点版本支持 。您还可以借助再生器(或使用再生器的工具,如 Babel)将代码转换为 ES5,从而支持较旧的环境。.async/await
回调是指将函数 1 传递给函数 2。函数 2 可以在函数 1 准备就绪时调用它。在异步进程的上下文中,每当异步进程完成时,都会调用回调。通常,结果会传递给回调。
在问题的示例中,您可以接受回调并将其用作回调。所以这个foosuccess
foosuccessvar result = foo();
// Code that depends on 'result'
becomes
foo(function(result) {
// Code that depends on 'result'
});
在这里,我们定义了函数“内联”,但您可以传递任何函数引用:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo foo本身定义如下:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback 将引用我们在调用它时传递给的函数,并将其传递给 .即一旦 Ajax 请求成功,将调用并将响应传递给回调(可以用 引用,因为这是我们定义回调的方式)。.foosuccess$.ajaxcallbackresult
您还可以在将响应传递给回调之前处理响应:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
使用回调编写代码比看起来更容易。毕竟,浏览器中的JavaScript在很大程度上是事件驱动的(DOM事件)。接收 Ajax 响应只不过是一个事件。
当您必须使用第三方代码时,可能会出现困难,但大多数问题都可以通过考虑应用程序流程来解决。
Promise API 是 ECMAScript 6 (ES2015) 的新功能,但它已经有很好的浏览器支持。还有许多库实现了标准的Promise API,并提供额外的方法来简化异步函数(例如bluebird)的使用和组合。
承诺是未来价值的容器。当承诺收到值(已解析)或取消(拒绝)时,它会通知所有想要访问此值的“侦听器”。
与普通回调相比,它的优点是它们允许您解耦代码并且它们更易于编写。
下面是使用承诺的示例:
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; }
应用于我们的 Ajax 调用,我们可以使用这样的承诺:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("https://jsonplaceholder.typicode.com/todos/1")
.then(function(result) {
console.log(result); // Code depending on result
})
.catch(function() {
// An error occurred
});
.as-console-wrapper { max-height: 100% !important; top: 0; }
描述 promise 提供的所有优势超出了本答案的范围,但如果您编写新代码,您应该认真考虑它们。它们提供了代码的出色抽象和分离。
有关承诺的更多信息:HTML5 rocks – JavaScript Promises.
延迟对象是jQuery对承诺的自定义实现(在Promise API标准化之前)。它们的行为几乎像承诺,但公开的 API 略有不同。
jQuery 的每个 Ajax 方法都已经返回了一个“延迟对象”(实际上是延迟对象的承诺),你可以从函数中返回它:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
请记住,承诺和延迟对象只是未来值的容器,它们不是值本身。例如,假设您有以下内容:
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
此代码误解了上述异步问题。具体来说,在检查服务器上的“/password”页面时不会冻结代码 – 它向服务器发送请求,并在等待时立即返回jQuery Ajax Deferred对象,而不是来自服务器的响应。这意味着该语句将始终获取此 Deferred 对象,将其视为 ,并像用户已登录一样继续操作。不好。.$.ajax()iftrue
但是修复很容易:
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
正如我所提到的,一些(!)异步操作具有同步对应项。我不主张使用它们,但为了完整起见,以下是执行同步调用的方法:
如果直接使用对象,请作为第三个参数传递给 。 .XMLHttpRequestfalse.open
如果使用 jQuery,则可以将选项设置为 。请注意,此选项从 jQuery 1.8 开始被弃用。
然后,您仍然可以使用回调或访问 jqXHR 对象的属性:asyncfalsesuccessresponseText
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
如果使用任何其他 jQuery Ajax 方法,例如 、 等,则必须将其更改为(因为只能将配置参数传递给 )。$.get$.getJSON$.ajax$.ajax
小心!无法发出同步 JSONP 请求。JSONP本质上总是异步的(甚至不考虑此选项的另一个原因)。

网友回答:
XMLHttpRequest 2(首先,阅读Benjamin Gruenbaum和Felix Kling的答案)
如果你不使用jQuery,并且想要一个在现代浏览器和移动浏览器中工作的简短XMLHttpRequest 2,我建议这样使用它:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
如您所见:
有两种方法可以获取此 Ajax 调用的响应(三种使用 XMLHttpRequest var 名称):
最简单的:
this.response
或者,如果由于某种原因您回调到一个类:bind()
e.target.response
例:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Or (the above one is better anonymous functions are always a problem):
ajax('URL', function(e){console.log(this.response)});
Nothing easier.
Now some people will probably say that it’s better to use onreadystatechange or the even the XMLHttpRequest variable name. That’s wrong.
Check out XMLHttpRequest advanced features.
It supported all *modern browsers. And I can confirm as I have been using this approach since XMLHttpRequest 2 was created. I never had any type of problem in any browsers I used.
onreadystatechange is only useful if you want to get the headers on state 2.
Using the variable name is another big error as you need to execute the callback inside the onload/oreadystatechange closures, or else you lost it.XMLHttpRequest
Now if you want something more complex using POST and FormData you can easily extend this function:
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.send(d||null)
}
Again … it’s a very short function, but it does GET and POST.
Examples of usage:
x(url, callback); // By default it's GET so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set POST data
Or pass a full form element ():document.getElementsByTagName('form')[0]
var fd = new FormData(form);
x(url, callback, 'post', fd);
Or set some custom values:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
As you can see, I didn’t implement sync… it’s a bad thing.
Having said that … why don’t we do it the easy way?
正如评论中提到的,错误&&同步的使用确实完全打破了答案的重点。哪种方式是正确使用Ajax的好方法?
错误处理程序
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.onerror = error;
c.send(d||null)
}
function error(e){
console.log('--Error--', this.type);
console.log('this: ', this);
console.log('Event: ', e)
}
function displayAjax(e){
console.log(e, this);
}
x('WRONGURL', displayAjax);
在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会影响函数。错误处理程序也可用于其他函数。
但是要真正摆脱错误,唯一的方法是编写错误的URL,在这种情况下,每个浏览器都会抛出错误。
如果您设置自定义标头、将响应类型设置为 blob 数组缓冲区或其他任何内容,错误处理程序可能会很有用。
即使您传递“POSTAPAPAP”作为方法,它也不会抛出错误。
即使您将“fdggdgilfdghfldj”作为formdata传递,它也不会抛出错误。
在第一种情况下,错误在 under as .displayAjax()this.statusTextMethod not Allowed
在第二种情况下,它只是工作。您必须在服务器端检查是否传递了正确的发布数据。
不允许跨域会自动引发错误。
在错误响应中,没有任何错误代码。
只有 哪个设置为错误。this.type
如果您完全无法控制错误,为什么要添加错误处理程序?
大多数错误都是在回调函数中返回的。displayAjax()
因此:如果您能够正确复制和粘贴 URL,则无需进行错误检查。?
PS:作为第一个测试,我写了x(’x’,displayAjax)…,它完全得到了响应…???所以我检查了HTML所在的文件夹,有一个名为“x.xml”的文件。因此,即使您忘记了文件的扩展名XMLHttpRequest 2也会找到它。我哈哈
同步读取文件
别这样。
如果你想阻止浏览器一段时间,加载一个漂亮的大文件同步。.txt
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
现在你可以做
var res = omg('thisIsGonnaBlockThePage.txt');
没有其他方法可以以非异步方式执行此操作。(是的,使用设置超时循环…但认真的?
另一点是…如果您使用 API 或仅使用您自己的列表文件或任何您总是为每个请求使用不同功能的内容……
仅当您有一个页面时,您始终加载相同的XML / JSON或任何只需要一个函数的页面。在这种情况下,稍微修改一下 Ajax 函数并将 b 替换为您的特殊函数。
以上功能仅供基本使用。
如果要扩展该功能…
是的,你可以。
我正在使用很多 API,我集成到每个 HTML 页面中的第一个函数是这个答案中的第一个 Ajax 函数,只有 GET …
但是你可以用XMLHttpRequest 2做很多事情:
我制作了一个下载管理器(使用简历、文件阅读器和文件系统的两侧范围)、使用画布的各种图像调整器转换器、使用 base64images 填充 Web SQL 数据库等等……
但是在这些情况下,您应该仅为此目的创建一个函数……有时你需要一个blob,数组缓冲区,你可以设置标题,覆盖mimetype,还有更多……
但这里的问题是如何返回 Ajax 响应……(我添加了一个简单的方法。
模板简介:该模板名称为【在JavaScript中我如何从异步调用返回响应?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【JavaScript】栏目查找您需要的精美模板。