我有一个foo
发出异步请求的函数。我如何从中返回响应/结果foo
?
我尝试从回调中返回值,以及将结果分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应(它们都返回undefined
或变量的初始值为任意值result
)
。
使用jQueryajax
函数的示例:
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`
}
在 一 中的Ajax代表
异步
。这意味着发送请求(或接收响应)已从正常执行流程中删除。在您的示例中,$.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
(ES2017 +,如果使用转译器或再生器,则在较旧的浏览器中可用)then()
(ES2015 +,如果您使用许多承诺库之一,则在较旧的浏览器中可用)在当前浏览器和节点7+中,所有这三个功能均可用。
async/await
2017年发布的ECMAScript版本引入了对异步功能的 语法级支持
。借助async
和await
,您可以以“同步样式”编写异步。该代码仍然是异步的,但更易于阅读/理解。
async/await
建立在promise之上:async
函数总是返回promise。await
“解包”一个承诺,或者导致承诺所解决的价值,或者如果该承诺被拒绝,则抛出错误。
重要提示:
您只能await
在async
函数内部使用。目前,await
尚不支持顶层,因此您可能必须进行异步IIFE(立即调用函数表达式)才能启动async
上下文。
你可以阅读更多关于async
和await
的MDN。
这是一个基于以上延迟的示例:
// 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 await 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);
})();
当前的浏览器和节点版本支持async/await
。您还可以通过使用再生器(或使用再生器的工具,例如Babel)将代码转换为ES5来支持较旧的环境。
回调只是传递给另一个函数的一个函数。该其他函数可以随时调用传递的函数。在异步过程的上下文中,只要完成异步过程,就会调用回调。通常,结果将传递给回调。
在问题的示例中,您可以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
。即,一旦Ajax请求成功,$.ajax
将调用callback
并将响应传递给回调(可以使用进行引用result
,因为这是我们定义回调的方式)。
您还可以在将响应传递给回调之前进行处理:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
使用回调编写代码比看起来容易。毕竟,浏览器中的JavaScript很大程度上是事件驱动的(DOM事件)。接收Ajax响应不过是一个事件。
当您必须使用第三方代码时,可能会遇到困难,但是大多数问题可以通过思考应用程序流程来解决。
US/docs/Web/JavaScript/Reference/Global_Objects/Promise)承诺
该承诺API是ECMAScript的6(ES2015)的新功能,但它有很好的浏览器支持了。还有许多实现标准Promises
API的库,并提供其他方法来简化异步函数(例如bluebird)的使用和组合。
承诺是 未来 价值的容器。当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).
});
应用于我们的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("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
描述promise提供的所有优点超出了此答案的范围,但是如果您编写新代码,则应认真考虑它们。它们为您的代码提供了很好的抽象和分离。
有关诺言的更多信息:HTML5摇滚-JavaScript
Promises
延迟对象是jQuery的promise的自定义实现(在PromiseAPI标准化之前)。它们的行为几乎像promise,但是暴露了稍微不同的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
}
此代码误解了上述异步问题。具体来说,$.ajax()
它不会在检查服务器上的“ / password”页面时冻结代码-它会向服务器发送请求,而在等待时,它会立即返回jQuery Ajax
Deferred对象,而不是服务器的响应。这意味着该if
语句将始终获取此Deferred对象,将其视为true
,然后像用户已登录一样继续进行。
但是解决方法很简单:
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
});
如前所述,某些异步操作具有同步的对应对象。我不主张使用它们,但是出于完整性考虑,这是执行同步调用的方式:
如果您直接使用XMLHttpRequest
对象,请false
作为第三个参数传递给.open
。
如果使用jQuery,则可以将async
选项设置为false
。请注意,自jQuery
1.8起 不推荐使用
此选项。然后,您仍然可以使用success
回调或访问jqXHR对象的responseText
属性:
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
如果使用任何其他的jQuery的Ajax的方法,例如$.get
,$.getJSON
等等,必须将其改为$.ajax
(因为你只能传递配置参数$.ajax
)。
小心! 不可能发出同步JSONP请求。JSONP本质上始终是异步的(还有一个甚至不考虑此选项的原因)
使用promise的块的示例:
问题内容: 我有一个发出Ajax请求的函数。我如何从中返回响应? 我尝试从回调中返回值,以及将响应分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应。 问题答案: 问题 在一中的Ajax代表异步。这意味着发送请求(或更确切地说接收响应)已从正常执行流程中删除。在你的示例中,立即返回并且在调用;作为回调传递的函数之前执行下一条语句。 这是一个类比,希望可以使同步流和异步流之间的区别
问题内容: 我在自己的类中拥有所有异步调用,因此我不想将aync’ly设置为全局变量。为此,我想从我的asunc postProcess方法返回对象,例如字符串。 能做到吗? 下面是我类的一般结构,例如,我想从onPostExecute()返回一个字符串。我看到在其他地方提到了委托,但这似乎很混乱,确定有办法为类或方法提供返回类型吗? 问题答案: 像下面这样 和听众课 你可以这样打电话
问题内容: 在SO中多次问过这个问题。但是我还是收不到东西。 我想从回调中获得一些价值。请看下面的脚本进行澄清。 如果我尝试返回该值,则只是“未定义”。我遵循了SO的一些想法,但仍然失败。 那些是: 问题答案: 这是不可能的,因为您无法从同步方法内部的异步调用返回。 在这种情况下,您需要将回调传递给foo,该回调将接收返回值 问题是,如果内部函数调用是异步的,则所有“包装”此调用的函数也必须是异步
例如 原因: 我想我必须禁用gson解析到POJO中,但不知道如何做到这一点。
问题内容: 如何从异步函数返回值?我试图喜欢这个 它给了我, 问题答案: 您不能超出范围。为了获得预期的结果,您应该将其包装到异步IIFE中,即 样品。 有关更多信息 由于返回一个Promise,因此可以将其省略,如下所示: 然后像以前一样做