我有一个函数foo
,它发出Ajax请求。 如何返回foo
的响应?
我尝试从success
回调中返回值,以及将响应分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应。
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- I tried that one as well
}
});
return result;
}
var result = foo(); // It always ends up being `undefined`.
→有关使用不同示例的异步行为的更一般性说明,请参见 为什么在函数内部修改变量后变量没有改变? -异步代码参考
→如果您已经了解问题,请跳至下面的可能解决方案。
Ajax中的A代表异步 。 这意味着发送请求(或接收响应)已从正常执行流程中删除。 在您的示例中, $.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();
之后的任何代码都将执行var item = findItem();
必须等到函数返回结果。
您出于相同的原因再次致电给您的朋友。 但是这次您告诉他您很着急,他应该用您的手机给您回电 。 您挂断电话,离开房屋,然后按计划做。 一旦您的朋友给您回电,您就可以处理他提供给您的信息。
这正是您执行Ajax请求时发生的事情。
findItem(function(item) {
// Do something with item
});
doSomethingElse();
无需等待响应,而是立即继续执行,并执行Ajax调用后的语句。 为了最终获得响应,您提供了一个在收到响应后立即调用的函数,即回调函数(注意什么? 回叫 ?)。 在调用之后执行的所有语句都将在调用回调之前执行。
拥抱JavaScript的异步特性! 尽管某些异步操作提供了同步对应项(“ Ajax”也是如此),但通常不建议使用它们,尤其是在浏览器上下文中。
你问为什么不好?
JavaScript在浏览器的UI线程中运行,任何长时间运行的进程都将锁定UI,从而使其无响应。 此外,JavaScript的执行时间有上限,浏览器会询问用户是否继续执行。
所有这些确实是糟糕的用户体验。 用户将无法判断一切是否正常。 此外,对于连接速度较慢的用户,效果会更糟。
在下面的内容中,我们将研究三种互为基础的不同解决方案:
async/await
承诺 (ES2017 +,如果使用转译器或再生器,则在较旧的浏览器中可用) then()
承诺 (ES2015 +,如果您使用许多promise库之一,则在较旧的浏览器中可用) 在当前浏览器和节点7+中,所有这三个功能均可用。
async/await
承诺 2017年发布的ECMAScript版本引入了对异步功能的语法级支持 。 借助async
和await
,您可以以“同步样式”编写异步。 该代码仍然是异步的,但更易于阅读/理解。
async/await
建立在promise之上: async
函数总是返回promise。 await
“解开”承诺,并导致承诺被解决的值,或者如果承诺被拒绝,则抛出错误。
重要提示:您只能在async
函数内使用await
。 目前,尚不支持顶层await
,因此您可能必须进行异步IIFE( 立即调用函数表达式 )才能启动async
上下文。
您可以阅读有关async
并在MDN上await
更多信息。
这是一个基于以上延迟的示例:
// 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响应不过是一个事件。
当您必须使用第三方代码时,可能会遇到困难,但是大多数问题可以通过思考应用程序流程来解决。
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的自定义实现(在Promise API标准化之前)。 它们的行为几乎像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
对象, .open
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本质上始终是异步的(还有一个甚至不考虑此选项的原因)。
您的代码应类似于以下内容:
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的人编写了答案,我决定为没有使用jQuery的人提供替代方案。
( 请注意,对于那些使用新的fetch
API,Angular或Promise的人,我在下面添加了另一个答案 )
这是另一个答案的“问题解释”的简短摘要,如果不确定阅读此内容后,请阅读该内容。
AJAX中的A代表异步 。 这意味着发送请求(或接收响应)已从正常执行流程中删除。 在您的示例中, .send
立即返回,下.send
语句return result;
会在您调用success
回调函数之前执行。
这意味着当您返回时,您定义的侦听器尚未执行,这意味着您要返回的值尚未定义。
这是一个简单的比喻
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
(小提琴)
的值a
返回的undefined
,因为a=5
部分还没有被执行。 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调用,在这种情况下为超时)。
用法是:
getFive(onComplete);
哪个应该在屏幕上提示“ 5”。 (提琴) 。
基本上有两种方法可以解决此问题:
至于同步AJAX, 请不要这样做! Felix的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。 总结起来,它将冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。 这是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);
}
让您的函数接受回调。 在示例代码中,可以使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并采取相应的措施(创建失败处理程序等)来进一步扩展此功能。 有效解决我们的问题。
如果您仍然难以理解,请阅读 MDN上的AJAX入门指南 。
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()
回调bind()
到一个类:
e.target.response
例:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
或者(上面的一个更好的匿名函数总是一个问题):
ajax('URL', function(e){console.log(this.response)});
没有比这容易的了。
现在,有些人可能会说,最好使用onreadystatechange甚至XMLHttpRequest变量名称。 错了
查看XMLHttpRequest的高级功能
它支持所有*现代浏览器。 我可以确认,因为XMLHttpRequest 2存在,所以我正在使用这种方法。 我使用的所有浏览器都从未遇到过任何类型的问题。
仅当您要获取状态2的标头时,onreadystatechange才有用。
使用XMLHttpRequest
变量名是另一个大错误,因为您需要在onload / oreadystatechange闭包内执行回调,否则会丢失它。
现在,如果您想使用post和FormData进行更复杂的操作,则可以轻松扩展此功能:
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)
}
再次...这是一个非常短的函数,但是它确实可以获取和发布。
用法示例:
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
或传递一个完整的表单元素( document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
或设置一些自定义值:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
如您所见,我没有实现同步...这是一件坏事。
话虽如此...为什么不做简单的事情呢?
如评论中所述,使用错误&&同步确实会完全破坏答案。 哪种是正确使用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,在这种情况下,每个浏览器都会抛出错误。
如果您设置自定义标头,将responseType设置为blob数组缓冲区或其他任何内容,则错误处理程序可能会很有用。
即使您将“ POSTAPAPAP”作为方法传递,它也不会引发错误。
即使您将'fdggdgilfdghfldj'作为formdata传递,它也不会引发错误。
在第一种情况下,错误是在this.statusText
下的displayAjax()
内部,因为Method not Allowed
。
在第二种情况下,它只是有效。 您必须在服务器端检查是否传递了正确的发布数据。
不允许跨域自动引发错误。
在错误响应中,没有错误代码。
只有this.type
设置为error。
如果您完全无法控制错误,为什么还要添加错误处理程序? 大多数错误都在回调函数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');
没有其他方法可以以非异步方式执行此操作。 (是的,使用setTimeout循环...但是认真吗?)
另一点是...如果您使用的是API或仅使用自己列表的文件,或者您总是对每个请求使用不同的功能...
仅当您有一个页面始终加载相同的XML / JSON或仅需要一个函数的页面时。 在这种情况下,请稍微修改Ajax函数并将b替换为您的特殊函数。
以上功能仅供基本使用。
如果要扩展功能...
是的你可以。
我使用了许多API,并且我集成到每个HTML页面中的第一个函数是此答案中的第一个Ajax函数,仅使用GET ...
但是,您可以使用XMLHttpRequest 2做很多事情:
我做了一个下载管理器(在两边使用简历,文件阅读器,文件系统使用范围),使用画布的各种图像缩放器转换器,使用base64images填充Web SQL数据库等等。但是在这些情况下,您应该为此创建一个函数目的...有时您需要一个blob,数组缓冲区,可以设置标头,覆盖mimetype等等,还有更多...
但是这里的问题是如何返回Ajax响应...(我添加了一种简单的方法。)
最简单的解决方案是创建一个JavaScript函数,并为Ajax success
回调调用它。
function callServerAsync(){
$.ajax({
url: '...',
success: function(response) {
successCallback(response);
}
});
}
function successCallback(responseObj){
// Do something like read the response and show data
alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}
function foo(callback) {
$.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
您使用的Ajax错误。 这个想法不是让它返回任何东西,而是将数据传递给称为回调函数的东西,该函数处理数据。
那是:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
在提交处理程序中返回任何内容都不会做任何事情。 相反,您必须交出数据,或者直接在成功函数中执行所需的操作。
对于使用AngularJS的人 ,可以使用Promises
处理这种情况。
在这里说
承诺可用于嵌套异步功能,并允许将多个功能链接在一起。
您也可以在这里找到一个很好的解释。
在下面提到的文档中找到示例。
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
在Angular2
,请看以下示例,但建议将Observables
与Angular2
一起使用。
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
你可以这样消耗掉
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
在此处查看原始帖子。 但是Typescript不支持本机es6 Promises ,如果要使用它,则可能需要插件。
另外,这里是在这里定义的Promise 规范 。
这意味着AngularJS,jQuery(具有延迟),本机XHR的替换(获取),EmberJS,BackboneJS的保存或任何返回promise的节点库。
您的代码应类似于以下内容:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Felix Kling很好地为使用jQuery和AJAX回调的人们编写了答案。 我对本地XHR有一个答案。 这个答案是针对promise在前端或后端的一般用法。
浏览器和具有NodeJS / io.js的服务器上的JavaScript并发模型是异步的和响应式的 。
每当调用返回承诺的方法时, then
处理程序始终异步执行-也就是说, 在它们下面的代码之后,该代码不在.then
处理程序中。
这意味着当您返回data
,您定义的then
处理程序尚未执行。 这又意味着您返回的值没有及时设置为正确的值。
这是一个简单的比喻:
function getFive(){ var data; setTimeout(function(){ // set a timer for one second in the future data = 5; // after a second, do this }, 1000); return data; } document.body.innerHTML = getFive(); // `undefined` here and not 5
由于尚未执行data = 5
部分,因此data
的值undefined
。 它可能会在一秒钟内执行,但到那时它与返回的值无关。
由于操作尚未发生(AJAX,服务器调用,IO,计时器),因此您将在请求有机会告诉您的代码该值之前返回该值。
一个可能的解决这个问题是代码重新活跃 ,告诉你的程序在计算完成后做什么。 承诺本质上是时间(时间敏感)的,因此可以积极地实现这一点。
承诺是一段时间的价值 。 承诺具有状态,它们以没有价值的待处理状态开始,可以解决:
一个承诺只能更改一次状态,此后它将永远永远保持在同一状态。 then
您可以将处理程序附加到Promise,以提取其值并处理错误。 then
处理程序允许链接调用。 通过使用返回API的API来创建Promise。 例如,更现代的AJAX替换fetch
或jQuery的$.get
返回承诺。
当我们调用.then
承诺并从中返回某些内容时-我们获得了处理后价值的承诺。 如果我们再次兑现诺言,我们将获得惊人的收获,但让我们坚持不懈。
让我们看看如何用诺言解决上述问题。 首先,让我们通过使用Promise构造函数创建延迟函数来从上面说明对承诺状态的理解:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
现在,在将setTimeout转换为使用Promise之后,可以使用then
进行计数:
function delay(ms){ // takes amount of milliseconds // returns a new promise return new Promise(function(resolve, reject){ setTimeout(function(){ // when the time is up resolve(); // change the promise to the fulfilled state }, ms); }); } function getFive(){ // we're RETURNING the promise, remember, a promise is a wrapper over our value return delay(100).then(function(){ // when the promise is ready return 5; // return the value 5, promises are all about return values }) } // we _have_ to wrap it like this in the call site, we can't access the plain value getFive().then(function(five){ document.body.innerHTML = five; });
基本上,不是返回由于并发模型而无法执行的值 ,而是返回一个包装器 ,该包装器可以使用then
进行解包 。 这就像一个盒子,你可以打开then
。
这与原始API调用相同,您可以:
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
因此,它也同样有效。 我们已经了解到无法从已经异步的调用中返回值,但是我们可以使用promise并将它们链接起来以执行处理。 现在,我们知道如何从异步调用返回响应。
ES6引入了生成器 ,这些生成器可以在中间返回然后再恢复它们所处的位置。 这通常对序列很有用,例如:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
是一个返回序列1,2,3,3,3,3,....
上可以迭代的迭代器的函数。 尽管这本身很有趣,并且为很多可能性打开了空间,但是有一个特别有趣的案例。
如果我们要生成的序列是一个动作序列而不是数字-我们可以在产生一个动作时暂停该函数,并在恢复该函数之前等待它。 因此,我们不需要一系列数字,而是需要一系列未来值-即:promise。
这个有点棘手但非常强大的技巧使我们可以以同步方式编写异步代码。 有几个“运行器”可以为您执行此操作,编写一小段代码即可,但超出了此答案的范围。 我将在这里使用Bluebird的Promise.coroutine
,但还有其他包装器,例如co
或Q.async
。
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
该方法本身返回一个promise,我们可以从其他协程中使用它。 例如:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
console.log(baz); // runs after both requests done
});
main();
在ES7中,这是进一步标准化的,目前有几个建议,但是您可以await
所有建议。 通过添加async
和await
关键字,这只是上述ES6提案的“糖”(更精细的语法)。 上面的例子:
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
它仍然返回一个相同的承诺:)
从异步函数返回值的另一种方法是传递一个对象,该对象将存储异步函数的结果。
这是一个相同的示例:
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
我正在使用result
对象在异步操作期间存储值。 这样即使在异步作业之后,结果仍然可用。
我经常使用这种方法。 我想知道这种方法在通过连续模块回传结果时效果如何。
简短的答案 :您的foo()
方法立即返回,而$ajax()
调用在函数返回后异步执行。 问题是一旦异步调用返回,如何或在何处存储结果。
在该线程中已经给出了几种解决方案。 也许最简单的方法是将一个对象传递给foo()
方法,并在异步调用完成后将结果存储在该对象的成员中。
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
请注意,对foo()
的调用仍然不会返回任何有用的信息。 但是,异步调用的结果现在将存储在result.response
。
我们发现自己处于一个似乎沿着我们称为“时间”的维度发展的宇宙中。 我们并不真正了解现在的时间,但是我们已经开发出抽象的概念和词汇,让我们进行推理和讨论:“过去”,“现在”,“未来”,“之前”,“之后”。
我们构建的计算机系统越来越多地将时间作为重要方面。 某些事情将在将来发生。 然后,在这些最初的事情最终发生之后,还需要发生其他事情。 这是称为“异步性”的基本概念。 在我们这个日益网络化的世界中,异步的最常见情况是等待某个远程系统响应某些请求。
考虑一个例子。 您打电话给送牛奶的人,点些牛奶。 到时,您想将其放入咖啡中。 您现在不能将牛奶放入咖啡中,因为现在还没有。 您必须等待它来之后再将其放入咖啡中。 换句话说,以下操作无效:
var milk = order_milk();
put_in_coffee(milk);
因为JS无法知道在执行put_in_coffee
之前需要等待 order_milk
完成。 换句话说,它不知道order_milk
是异步的-直到将来某个时候才会产生牛奶。 JS和其他声明性语言无需等待即可执行另一个语句。
利用此事实的经典JS方法,即JS支持将函数作为可传递的一流对象的事实,是将函数作为参数传递给异步请求,并在异步请求完成后调用它的任务在将来的某个时候。 那就是“回调”方法。 看起来像这样:
order_milk(put_in_coffee);
order_milk
开始,订购牛奶,然后仅在牛奶到达时才调用put_in_coffee
。
这种回调方法的问题是它污染了函数的正常语义,并用return
报告结果; 相反,函数不得通过调用作为参数给出的回调来报告其结果。 而且,在处理较长的事件序列时,此方法可能很快变得笨拙。 例如,假设我要等待牛奶放入咖啡中,然后再执行第三步,即喝咖啡。 我最终需要写这样的东西:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
我将传递牛奶的位置传递给put_in_coffee
,并将传递牛奶的操作( drink_coffee
)传递给put_in_coffee
。这样的代码变得很难编写,读取和调试。
在这种情况下,我们可以将问题中的代码重写为:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
这是“诺言”概念的动机,“诺言”是一种特殊的价值类型,代表某种未来或异步结果。 它可以表示已经发生的事情,或者将来会发生的事情,或者可能永远不会发生的事情。 Promise具有一个名为then
方法,当实现了Promise表示的结果时,您将向该方法传递要执行的动作。
对于我们的牛奶和咖啡,我们设计order_milk
以返回牛奶到达的承诺,然后将put_in_coffee
指定为then
操作,如下所示:
order_milk() . then(put_in_coffee)
这样的优点之一是我们可以将它们串在一起以创建将来出现的序列(“链接”):
order_milk() . then(put_in_coffee) . then(drink_coffee)
让我们将诺言应用于您的特定问题。 我们将请求逻辑包装在一个函数中,该函数返回一个Promise:
function get_data() {
return $.ajax('/foo.json');
}
实际上,我们所做的只是添加了对$.ajax
的调用return
。 之所以可行,是因为jQuery的$.ajax
已经返回了一种类似于promise的东西。 (在实践中,我们不愿赘述,而是希望包装此调用以便返回真实的Promise,或使用$.ajax
替代方法执行此操作。)现在,如果我们要加载文件并等待它完成然后做某事,我们可以简单地说
get_data() . then(do_something)
例如,
get_data() .
then(function(data) { console.log(data); });
当使用诺言时,我们最终会将大量函数传递给then
,因此使用更紧凑的ES6风格的箭头函数通常会有所帮助:
get_data() .
then(data => console.log(data));
async
关键字 但是,如果必须以一种同步方式编写代码,而以异步方式编写一种完全不同的方法,仍然存在一些含糊的不满。 对于同步,我们写
a();
b();
但是,如果a
是异步的,则必须保证我们必须编写
a() . then(b);
上面我们说过:“ JS无法知道它需要等待第一个调用完成才能执行第二个调用”。 那岂不是很好,如果有某种方式来告诉JS呢? 事实证明,在特殊类型的函数(称为“异步”函数)中使用了await
关键字。 该功能是ES即将发布的版本的一部分,但在正确的预设下,已经可以在Babel等转译器中使用。 这使我们可以简单地编写
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
就您而言,您将可以编写如下内容
async function foo() {
data = await get_data();
console.log(data);
}
虽然promise和callback在许多情况下都可以正常工作,但是表达类似以下内容很麻烦:
if (!name) {
name = async1();
}
async2(name);
您最终将经历async1
; 检查name
是否未定义,然后相应地调用回调。
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
虽然在一些小示例中还可以 ,但是当您遇到很多类似的情况和错误处理时,它会变得很烦人。
Fibers
有助于解决问题。
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
您可以在此处签出项目。
我编写的以下示例显示了如何
这个工作示例是独立的。 它将定义一个简单的请求对象,该对象使用窗口XMLHttpRequest
对象进行调用。 它将定义一个简单的函数来等待一堆承诺完成。
上下文。 该示例正在查询Spotify Web API端点,以便为给定的查询字符串集搜索playlist
对象:
[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
对于每个项目,一个新的Promise将触发一个块ExecutionBlock
,解析结果,基于结果数组(即Spotify user
对象列表)安排新的Promise集,并在ExecutionProfileBlock
异步执行新的HTTP调用。
然后,您可以看到一个嵌套的Promise结构,该结构使您可以生成多个且完全异步的嵌套HTTP调用,并通过Promise.all
每个调用子集的结果Promise.all
。
注意最近的Spotify search
API要求在请求标头中指定访问令牌:
-H "Authorization: Bearer {your access token}"
因此,要运行以下示例,需要将访问令牌放入请求标头中:
var spotifyAccessToken = "YourSpotifyAccessToken"; var console = { log: function(s) { document.getElementById("console").innerHTML += s + "
" } } // Simple XMLHttpRequest // based on https://davidwalsh.name/xmlhttprequest SimpleRequest = { call: function(what, response) { var request; if (window.XMLHttpRequest) { // Mozilla, Safari, ... request = new XMLHttpRequest(); } else if (window.ActiveXObject) { // Internet Explorer try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } // State changes request.onreadystatechange = function() { if (request.readyState === 4) { // Done if (request.status === 200) { // Complete response(request.responseText) } else response(); } } request.open('GET', what, true); request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken); request.send(null); } } //PromiseAll var promiseAll = function(items, block, done, fail) { var self = this; var promises = [], index = 0; items.forEach(function(item) { promises.push(function(item, i) { return new Promise(function(resolve, reject) { if (block) { block.apply(this, [item, index, resolve, reject]); } }); }(item, ++index)) }); Promise.all(promises).then(function AcceptHandler(results) { if (done) done(results); }, function ErrorHandler(error) { if (fail) fail(error); }); }; //promiseAll // LP: deferred execution block var ExecutionBlock = function(item, index, resolve, reject) { var url = "https://api.spotify.com/v1/" url += item; console.log( url ) SimpleRequest.call(url, function(result) { if (result) { var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) { return item.owner.href; }) resolve(profileUrls); } else { reject(new Error("call error")); } }) } arr = [ "search?type=playlist&q=%22doom%20metal%22", "search?type=playlist&q=Adele" ] promiseAll(arr, function(item, index, resolve, reject) { console.log("Making request [" + index + "]") ExecutionBlock(item, index, resolve, reject); }, function(results) { // Aggregated results console.log("All profiles received " + results.length); //console.log(JSON.stringify(results[0], null, 2)); / promiseall again var ExecutionProfileBlock = function(item, index, resolve, reject) { SimpleRequest.call(item, function(result) { if (result) { var obj = JSON.parse(result); resolve({ name: obj.display_name, followers: obj.followers.total, url: obj.href }); } //result }) } //ExecutionProfileBlock promiseAll(results[0], function(item, index, resolve, reject) { //console.log("Making request [" + index + "] " + item) ExecutionProfileBlock(item, index, resolve, reject); }, function(results) { // aggregated results console.log("All response received " + results.length); console.log(JSON.stringify(results, null, 2)); } , function(error) { // Error console.log(error); }) / }, function(error) { // Error console.log(error); });
我在这里广泛讨论了该解决方案。
简短的答案是, 您必须实现这样的回调:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
您可以使用此自定义库(使用Promise编写)来进行远程调用。
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
简单用法示例:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
看一下这个例子:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
如您所见, getJoke
返回一个已解决的promise (返回res.data.value
时已解决)。 因此,您可以等待$ http.get请求完成,然后执行console.log(res.joke) (作为常规的异步流程)。
这是plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6方式(异步-等待)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
我将以恐怖的手绘漫画来回答。 第二张图片是代码示例中undefined
result
的原因。
var App = App || {}; App = { getDataFromServer: function(){ var self = this, deferred = $.Deferred(), requests = []; requests.push($.getJSON('request/ajax/url/1')); requests.push($.getJSON('request/ajax/url/2')); $.when.apply(jQuery, requests).done(function(xhrResponse) { return deferred.resolve(xhrResponse.result); }); return deferred; }, init: function(){ this.getDataFromServer().done(_.bind(function(resp1, resp2) { // Do the operations which you wanted to do when you // get a response from Ajax, for example, log response. }, this)); } }; App.init();
在foo()
成功内使用callback()
函数。 以这种方式尝试。 它简单易懂。
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
这里的大多数答案都为您执行单个异步操作提供了有用的建议,但是有时,当您需要对数组或其他类似列表的结构中的每个条目执行异步操作时,就会出现这种情况。 诱惑在于:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
例:
// WRONG var theArray = [1, 2, 3]; var results = []; theArray.forEach(function(entry) { doSomethingAsync(entry, function(result) { results.push(result); }); }); console.log("Results:", results); // Eg, using them, returning them, etc. function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
无效的原因是,在您尝试使用结果时, doSomethingAsync
的回调尚未运行。
因此,如果您有一个数组(或某种类型的列表),并且想对每个条目执行异步操作,则有两个选择:并行(重叠)或串行(一个接一个地依次执行)。
您可以启动所有这些,并跟踪期望的回调数量,然后在获得许多回调时使用结果:
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
例:
var theArray = [1, 2, 3]; var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! console.log("Results:", results); // Eg, using the results } }); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
(我们可以消除expecting
,只使用results.length === theArray.length
,但这使我们对在调用未完成时更改theArray
的可能性theArray
开放theArray
。)
注意,我们如何使用forEach
的index
将结果保存在与它相关的条目相同位置的results
中,即使结果到达的顺序不正确(因为异步调用不一定按开始的顺序完成) )。
但是,如果您需要从函数返回这些结果怎么办? 正如其他答案所指出的那样,您不能这样做。 您必须让您的函数接受并调用回调(或返回Promise )。 这是一个回调版本:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray, callback) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! callback(results); } }); }); } doSomethingWith([1, 2, 3], function(results) { console.log("Results:", results); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
或以下是返回Promise
的版本:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
当然,如果doSomethingAsync
我们传递了错误,则当出现错误时,我们将使用reject
来拒绝承诺。)
例:
function doSomethingWith(theArray) { return new Promise(function(resolve) { var results = []; var expecting = theArray.length; theArray.forEach(function(entry, index) { doSomethingAsync(entry, function(result) { results[index] = result; if (--expecting === 0) { // Done! resolve(results); } }); }); }); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
(或者,您可以为doSomethingAsync
做一个包装,该包装返回一个Promise,然后执行以下操作……)
如果doSomethingAsync
给您Promise ,则可以使用Promise.all
:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
如果您知道doSomethingAsync
将忽略第二个和第三个参数,则可以直接将其传递给map
( map
使用三个参数调用其回调,但是大多数人在大多数时间只使用第一个参数):
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) { return Promise.all(theArray.map(doSomethingAsync)); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
请注意, Promise.all
解决所有诺言时提供给您的所有诺言的结果数组来解决其诺言,或者在您给它的第一个诺言被拒绝时拒绝其诺言。
假设您不希望这些操作并行进行? 如果要一个接一个地运行它们,则需要等待每个操作完成后才能开始下一个操作。 这是一个函数的示例,该函数执行该操作并使用结果调用回调:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
(由于我们正在进行系列工作,因此我们可以使用results.push(result)
因为我们知道不会使结果乱序。在上面,我们可以使用results[index] = result;
但是在以下某些示例中,我们没有要使用的索引。)
例:
function doSomethingWith(theArray, callback) { var results = []; doOne(0); function doOne(index) { if (index < theArray.length) { doSomethingAsync(theArray[index], function(result) { results.push(result); doOne(index + 1); }); } else { // Done! callback(results); } } } doSomethingWith([1, 2, 3], function(results) { console.log("Results:", results); }); function doSomethingAsync(value, callback) { console.log("Starting async operation for " + value); setTimeout(function() { console.log("Completing async operation for " + value); callback(value * 2); }, Math.floor(Math.random() * 200)); }
.as-console-wrapper { max-height: 100% !important; }
(或者再次,为doSomethingAsync
构建包装器,该包装器会给您一个承诺并执行以下操作...)
如果doSomethingAsync
给您一个承诺,如果您可以使用ES2017 +语法(也许使用像Babel这样的编译器),则可以将async
函数与for-of
和await
:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
例:
async function doSomethingWith(theArray) { const results = []; for (const entry of theArray) { results.push(await doSomethingAsync(entry)); } return results; } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
如果还不能使用ES2017 +语法,则可以对“ Promise reduce”模式使用变体(这比通常的Promise reduce更为复杂,因为我们没有将结果从一个传递到下一个将结果收集到一个数组中):
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) { return theArray.reduce(function(p, entry) { return p.then(function(results) { return doSomethingAsync(entry).then(function(result) { results.push(result); return results; }); }); }, Promise.resolve([])); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
...使用ES2015 +箭头功能不太麻烦:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
例:
function doSomethingWith(theArray) { return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => { results.push(result); return results; })), Promise.resolve([])); } doSomethingWith([1, 2, 3]).then(function(results) { console.log("Results:", results); }); function doSomethingAsync(value) { console.log("Starting async operation for " + value); return new Promise(function(resolve) { setTimeout(function() { console.log("Completing async operation for " + value); resolve(value * 2); }, Math.floor(Math.random() * 200)); }); }
.as-console-wrapper { max-height: 100% !important; }
这是在许多新的JavaScript框架中使用的数据绑定或存储概念 两种方式将非常适合您的地方之一...
因此,如果您使用的是Angular,React或任何其他通过两种方式进行数据绑定或存储概念的框架,则此问题仅为您解决,因此,简单来说,您的结果在第一阶段是undefined
的,因此您得到了result = undefined
在接收数据之前,一旦得到结果,它将被更新并分配给您的Ajax调用响应的新值...
但是,如您在此问题中所提出的那样,如何用纯JavaScript或jQuery实现它呢?
您可以使用callback , promise和最近可观察到的回调来为您处理它,例如在promise中,我们有一些函数,例如success()
或then()
,这些函数将在您的数据准备就绪后执行,与callback或subscription函数相同可以观察 。
例如,在使用jQuery的情况下,可以执行以下操作:
$(document).ready(function(){
function foo() {
$.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
有关更多信息,请参阅有关promise和observable的信息,这是执行异步操作的新方法。
另一种解决方案是通过顺序执行程序nsynjs执行代码。
nsynjs将顺序评估所有promise,并将promise结果放入data
属性中:
function synchronousCode() { var getURL = function(url) { return window.fetch(url).data.text().data; }; var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'; console.log('received bytes:',getURL(url).length); }; nsynjs.run(synchronousCode,{},function(){ console.log('synchronousCode done'); });
步骤1.将带有回调的函数包装到可感知nsynjs的包装器中(如果它具有约定的版本,则可以跳过此步骤):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
步骤2.将同步逻辑放入功能中:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
步骤3.通过nsynjs以同步方式运行函数:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs将逐步评估所有运算符和表达式,以防某些慢速函数的结果尚未就绪时暂停执行。
此处有更多示例: https : //github.com/amaksr/nsynjs/tree/master/examples
这很简单:
这是您的代码的有效版本:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
当前所有浏览器和节点8中都支持await
当然,有许多方法,例如同步请求,promise,但是根据我的经验,我认为您应该使用回调方法。 Java的异步行为是很自然的。 因此,您的代码段可以重写一些:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
在看树之前先看看森林。
这里有许多信息丰富的答案和细节,我不会重复其中任何一个。 在JavaScript中编程的关键是首先要有正确的整体执行心智模型 。
好消息是,如果你理解这一点,你将永远不必担心竞争条件。 首先,您应该如何组织代码本质上是对不同离散事件的响应,以及您希望如何将它们组合成逻辑序列。 您可以使用promises或更高级别的新异步/等待作为工具,或者您可以自己动手。
但是,在熟悉实际问题域之前,不应使用任何战术工具来解决问题。 绘制这些依赖关系的映射,以了解何时需要运行。 尝试对所有这些回调采用临时方法并不能很好地为您服务。
在挣扎着JavaScript的“奥秘”时,这是我们面临的一个非常普遍的问题。 今天让我尝试揭开这个谜团的神秘面纱。
让我们从一个简单的JavaScript函数开始:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
这是一个简单的同步函数调用(其中每一行代码均按顺序在下一行之前“完成其工作”),并且结果与预期的相同。
现在,通过在函数中引入很少的延迟来增加一点扭曲,以便所有代码行都不会按顺序“完成”。 因此,它将模拟功能的异步行为:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
这样一来,延迟就破坏了我们期望的功能! 但是到底发生了什么? 好吧,如果您看一下代码,这实际上是很合逻辑的。 函数foo()
在执行时不返回任何内容(因此返回的值是undefined
),但是它确实启动了一个计时器,该计时器在1秒后执行一个函数以返回“ wohoo”。 但是正如您所看到的,分配给bar的值是foo()立即返回的内容,而不是随后出现的其他任何内容。
那么,我们如何解决这个问题呢?
让我们问一下函数PROMISE 。 Promise的确是关于它的含义:它意味着该函数保证您提供将来得到的任何输出。 因此,让我们针对上面的小问题在实际中进行观察:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
因此,摘要是-为处理异步功能(例如基于ajax的调用等),您可以使用promise来resolve
值(打算返回)。 因此,简而言之,您可以在异步函数中解析值而不是返回 。
除了使用then/catch
来实现诺言之外,还存在另一种方法。 这个想法是识别异步函数 ,然后等待承诺解决,然后再转到下一行代码。 它仍然只是promises
,但是采用了不同的句法方法。 为了使事情更清楚,您可以在下面找到一个比较:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
throw err;
})
}
async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
throw err;
}
}
问题是:
如何从异步调用返回响应?
可以解释为:
如何使异步代码看起来同步 ?
解决方案是避免回调,并结合使用Promises和async / await 。
我想举一个Ajax请求的例子。
(尽管它可以用Javascript编写,但我更喜欢用Python编写,然后使用Transcrypt将其编译为Javascript。这已经足够清楚了。)
首先启用JQuery用法,使$
可以用作S
:
__pragma__ ('alias', 'S', '$')
定义一个返回Promise的函数,在本例中为Ajax调用:
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
使用异步代码,就好像它是同步的 :
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
使用ES2017,您应该将此作为函数声明
async function foo() {
var response = await $.ajax({url: '...'})
return response;
}
并像这样执行它。
(async function() {
try {
var result = await foo()
console.log(result)
} catch (e) {}
})()
或Promise语法
foo().then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
Js是单线程的。
浏览器可以分为三个部分:
1)事件循环
2)Web API
3)事件队列
事件循环永远运行,即无限循环。事件队列是将所有功能推送到某个事件(例如单击)的地方,这是逐个执行的,并放入事件循环中,该循环执行该功能并自行准备对于第一个函数执行完之后的下一个函数,这意味着直到事件循环中执行该函数的队列中的函数才开始执行一个函数。
现在让我们认为我们在队列中推送了两个函数,一个是从服务器获取数据,另一个是利用该数据。我们先在队列中推送了serverRequest()函数,然后是utiliseData()函数。 serverRequest函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据将花费多少时间,因此预计此过程将花费一些时间,因此我们忙于事件循环,从而挂起了页面,这就是Web API发挥作用,它将事件循环中的此功能带入服务器,并与服务器释放事件循环,以便我们可以从队列中执行下一个功能。队列中的下一个功能是utiliseData(),它进入了循环,但由于没有可用数据,它进入了浪费,下一个函数的执行一直持续到队列结束(这称为异步调用,即我们可以做其他事情直到获得数据)
假设我们的serverRequest()函数在代码中有一个return语句,当我们从服务器Web API取回数据时,它将在队列末尾将其压入队列。 由于它在队列末尾被推送,因此我们无法利用其数据,因为队列中没有剩余功能可以利用此数据。 因此,不可能从异步调用返回某些内容。
因此,解决方案是回调或Promise 。
来自此处答案之一的图像,正确解释了回调的使用...我们将函数(利用从服务器返回的数据的函数)提供给函数调用服务器。
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
在我的代码中,它称为
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
在此处阅读ECMA(2016/17)中进行异步调用的新方法(@Felix Kling顶部答复) https://stackoverflow.com/a/14220323/7579856
ECMAScript 6具有“生成器”,使您可以轻松地以异步样式进行编程。
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
要运行以上代码,请执行以下操作:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
如果您需要针对不支持ES6的浏览器,则可以通过Babel或闭包编译器运行代码以生成ECMAScript 5。
回调...args
包裹在数组中,并在您读取它们时进行了结构分解,以便该模式可以处理具有多个参数的回调。 例如,使用节点fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
除了了解代码之外,还有2个概念是理解JS如何处理回调和异步性的关键。 (哪怕一个字?)
您需要注意三件事: 队列; 事件循环和堆栈
概括地说,事件循环就像项目管理器一样,它一直在侦听要运行的任何功能,并在队列和堆栈之间进行通信。
while (queue.waitForMessage()) {
queue.processNextMessage();
}
一旦收到运行某条消息的消息,就会将其添加到队列中。 队列是等待执行的事物的列表(例如您的AJAX请求)。 像这样想象:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
当这些消息之一要执行时,它会从队列中弹出消息并创建一个堆栈,而该堆栈就是JS执行该消息中的指令所需的一切。 因此,在我们的示例中,它被告知调用foobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
因此,foobarFunc需要执行的所有操作(在我们的示例中为anotherFunction
)都将被压入堆栈。 执行,然后被遗忘-事件循环将移至队列中的下一件事(或侦听消息)
这里的关键是执行顺序。 那是
当您使用AJAX呼叫外部方或运行任何异步代码(例如setTimeout)时,JavaScript依赖于响应才能继续。
最大的问题是,它将何时获得响应? 答案是我们不知道-事件循环正在等待该消息说“嘿,我快跑”。 如果JS只是同步地等待该消息,则您的应用程序将冻结,并且很烂。 因此,JS在等待消息添加回队列的同时继续执行队列中的下一项。
这就是为什么在异步功能中我们使用了称为callbacks的东西。 从字面上看,这有点像一个承诺 。 就像我保证在某个时候返回某些内容一样, jQuery使用了称为deffered.done
deffered.fail
和deffered.always
特定回调(以及其他)。 你可以在这里看到他们
因此,您需要做的是传递一个函数,该函数应在传递给它的数据的某个点执行。
因为回调不是立即执行,而是在以后执行,所以将引用传递给未执行的函数很重要。 所以
function foo(bla) {
console.log(bla)
}
因此,大多数情况下(但并非总是如此),您将传递foo
而不是foo()
希望这会有所道理。 当您遇到这样令人困惑的事情时,我强烈建议您完整阅读文档以至少了解它。 这将使您成为更好的开发人员。
这个问题的最完美答案是使用Promise
。
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
使用诺言有问题!
我一直使用此解决方案一段时间,直到发现旧浏览器中出现错误:
Uncaught ReferenceError: Promise is not defined
因此,我决定为未定义的js编译器为ES3实现我自己的Promise类。 只需在您的主要代码之前添加此代码,然后安全地使用Promise!
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}