如何从异步调用返回响应?

我有一个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代表异步.这意味着发送请求(或者更确切地说是接收响应)将从正常执行流程中取出.在您的示例中,$.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 item
});
doSomethingElse();

而不是等待响应,执行立即继续执行Ajax调用之后的语句.为了得到响应,最终,你提供了一次收到答复要调用的函数,一个回调(注意些什么呢?叫回来?).在调用回调之前执行该调用之后的任何语句.


解决方案(S)

拥抱JavaScript的异步特性!虽然某些异步操作提供了同步对应("Ajax"也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中.

你问为什么这么糟糕?

JavaScript在浏览器的UI线程中运行,任何长时间运行的进程都会锁定UI,使其无响应.此外,JavaScript的执行时间有一个上限,浏览器会询问用户是否继续执行.

所有这些都是非常糟糕的用户体验.用户将无法判断一切是否正常.此外,连接速度慢的用户效果会更差.

在下文中,我们将看到三个不同的解决方案,它们都是相互叠加的:

  • 承诺async/await(ES2017 +,如果您使用转换器或再生器,可在旧版浏览器中使用)
  • 回调(在节点中很流行)
  • 承诺then()(ES2015 +,如果您使用众多承诺库中的一个,则可在旧版浏览器中使用)

这三个都在当前浏览器和节点7+中可用.


ES2017 +:承诺与 async/await

2017年发布的ECMAScript版本引入了异步函数的语法级支持.的帮助下asyncawait,你可以写在"同步式"异步的.代码仍然是异步的,但它更容易阅读/理解.

async/await建立在承诺之上:async函数总是返回一个承诺.await"解包"一个承诺,或者导致承诺被解决的价值,或者如果承诺被拒绝则抛出错误.

重要提示:您只能awaitasync函数内部使用.目前,await尚未支持顶级,因此您可能必须创建异步IIFE才能启动async上下文.

你可以阅读更多关于asyncawait的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'
});

这里我们定义了函数"inline",但你可以传递任何函数引用:

function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);

foo 本身定义如下:

function foo(callback) {
$.ajax({
// ...
success: callback
});
}

callbackfoo在我们调用它时引用我们传递给它的函数,我们只是将其传递给success.即,一旦Ajax请求成功,$.ajax将调用callback并将响应传递给回调(可以参考result,因为这是我们定义回调的方式).

您还可以在将响应传递给回调之前处理响应:

function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}

使用回调编写代码比使用它更容易.毕竟,浏览器中的JavaScript是由事件驱动的(DOM事件).接收Ajax响应只不过是一个事件.
当您必须使用第三方代码时可能会出现困难,但大多数问题都可以通过思考应用程序流来解决.


ES2015 +:承诺然后()

该承诺API是ECMAScript的6(ES2015)的新功能,但它有很好的浏览器支持了.还有许多库实现了标准的Promises API,并提供了其他方法来简化异步函数(例如bluebird)的使用和组合.

承诺是未来价值观的容器.当promise接收到值(已解决)或取消(拒绝)时,它会通知所有想要访问此值的"侦听器".

普通回调的优势在于它们允许您解耦代码并且更容易编写.

这是一个使用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调用,我们可以使用这样的promises:

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
});

描述承诺提供的所有优点超出了本答案的范围,但如果您编写新代码,则应认真考虑它们.它们提供了很好的抽象和代码分离.

关于promises的更多信息:HTML5 rocks - JavaScript Promises

旁注:jQuery的延迟对象

延迟对象是jQuery的promises自定义实现(在Promise API标准化之前).它们的行为几乎与承诺相似,但暴露出略微不同的API.

jQuery的每个Ajax方法都已经返回一个"延迟对象"(实际上是一个延迟对象的承诺),你可以从你的函数返回:

function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});

旁注:承诺陷阱

请记住,promises和deferred对象只是未来值的容器,它们本身并不是值.例如,假设您有以下内容:

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
});

不推荐:同步"Ajax"调用

正如我所提到的,一些(!)异步操作具有同步对应物.我不主张使用它们,但为了完整起见,以下是执行同步调用的方法:

没有jQuery

如果直接使用XMLHTTPRequest对象,则将false第三个参数传递给.open.

jQuery的

如果使用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本质上总是异步的(甚至不考虑这个选项的另一个原因).

  • @Pommy:如果你想使用jQuery,你必须包含它.请参阅http://docs.jquery.com/Tutorials:Getting_Started_with_jQuery.
  • 这个问题的聊天已经死了,所以我不确定在哪里提出概述的更改,但我建议:1)将同步部分更改为简单的讨论,为什么它很糟糕,没有代码示例如何做到这一点.2)删除/合并回调示例以仅显示更灵活的延迟方法,我认为对于那些学习Javascript的人来说也可能更容易理解.
  • @gibberish:嗯,我不知道如何让它更清晰.你看到如何调用`foo`并将一个函数传递给它(`foo(function(result){....});`)?`result`在此函数中使用,是Ajax请求的响应.要引用此函数,foo的第一个参数称为`callback`并分配给`success`而不是匿名函数.因此,当请求成功时,`$ .ajax`将调用`callback`.我试着解释一下.
  • @Jessi:我觉得你误解了那部分答案.如果希望Ajax请求是同步的,则不能使用`$ .getJSON`.但是,您不应该希望请求是同步的,因此不适用.您应该使用回调或承诺来处理响应,如前面的答案中所述.
  • 在解决方案1中,子jQuery,我无法理解这一行:`如果你使用任何其他jQuery AJAX方法,比如$ .get,$ .getJSON等,你可以使用$ .ajax.(是的,我意识到我的昵称在这种情况下有点讽刺)
  • @stom:*“两者都相同” *在示例中,它们指的是相同的值。*“您的意思是`成功:myCallback(响应)`” *不。我确实的意思是`成功:回调`。callback是foo参数的名称,参数的值分配给成功。“ myCallback”是传递给“ foo”的函数的名称,但这无关紧要。考虑这个简化的例子:`var x = 42; 函数foo(y){console.log(y); }; foo(x);`。y是foo的参数,因此使用了console.log(y)。我们可以*做`console.log(x);`,但这违背了函数的目的。
  • @stom:`foo(myCallback)`已经被调用。这就是发起Ajax调用的原因。但是,是的,当收到响应时,jQuery将调用分配给“成功”的函数并将响应传递给它。

如果您没有在代码中使用jQuery,那么这个答案就适合您

你的代码应该是这样的:

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的人提供替代方案.

(注意,对于那些使用新fetchAPI,Angular或promises的人,我在下面添加了另一个答案)


你面对的是什么

这是另一个答案的"问题解释"的简短摘要,如果您在阅读本文后不确定,请阅读.

AJAX中的A代表异步.这意味着发送请求(或者更确切地说是接收响应)将从正常执行流程中取出.在您的示例中,.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"到屏幕.(小提琴).

可能的解决方案

基本上有两种解决方法:

  1. 使AJAX调用同步(让我们称之为SJAX).
  2. 重构代码以使用回调正常工作.

1.同步AJAX - 不要这样做!!

至于同步AJAX,不要这样做!费利克斯的回答提出了一些令人信服的论据,说明为什么这是一个坏主意.总而言之,它会冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验.以下是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);
}

2.重组代码

让你的函数接受回调.在示例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入门指南.

  • "同步请求会阻止代码的执行并可能泄漏内存和事件"同步请求如何泄漏内存?
  • 仅供参考,XHR 2允许我们使用`onload`处理程序,该处理程序仅在`readyState`为`4`时触发.当然,它在IE8中不受支持.(iirc,可能需要确认.)
  • @MatthewG我在[这个问题](http://stackoverflow.com/questions/14364992/how-synchronous-ajax-call-could-cause-memory-leak)中添加了一笔赏金,我会看到什么我可以钓鱼了.我正在删除答案中的引文.
  • 您对如何将匿名函数作为回调传递的说明是有效的但具有误导性.示例var bar = foo(); 要求定义变量,而建议的foo(functim(){}); 没有定义吧
  • I can see why this might be a little misleading, but after months of not knowing the answer to this question, I finally understand what's going on and how to avoid it (you just want to make your async functions not use any public variables and pass their return data to functions with their own private variables). I have no idea why this answer lead me to this/my solution, but it definitely did. Thanks man! I also realize this question may not be exactly asking what I'm describing, but it's at least related/similar.

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()
}

如你看到的:

  1. 它比列出的所有其他功能都短.
  2. 回调是直接设置的(因此没有额外的不必要的闭包).
  3. 它使用新的onload(因此您不必检查readystate && status)
  4. 还有其他一些我不记得的情况会让XMLHttpRequest 1变得烦人.

有两种方法可以获得此Ajax调用的响应(三种使用XMLHttpRequest var名称):

最简单的:

this.response

或者,如果由于某种原因你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存在.在我使用的所有浏览器上,我从未遇到任何类型的问题.

onreadystatechange仅在您希望获取状态2的标头时才有用.

使用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);

如你所见,我没有实现同步...这是一件坏事.

话虽如此......为什么不这么简单呢?


正如评论中所提到的,使用error && synchronous确实完全打破了答案的要点.哪个是以正确方式使用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传递它也不会抛出错误.

在第一种情况下,错误在displayAjax()under this.statusTextas中Method 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');

没有其他方法可以以非异步方式执行此操作.(是的,使用setTimeout循环......但是认真吗?)

另一点是......如果你使用API​​或只是你自己的列表文件或者你总是为每个请求使用不同的函数...

只有当你有一个页面,你总是加载相同的XML/JSON或任何你只需要一个函数.在这种情况下,修改一点Ajax函数并用您的特殊函数替换b.


以上功能仅供基本使用.

如果你想扩展功能......

是的你可以.

我使用了很多API,我在每个HTML页面中集成的第一个函数之一是这个答案中的第一个Ajax函数,只有GET ...

但是你可以用XMLHttpRequest 2做很多事情:

我创建了一个下载管理器(使用范围,包括简历,文件读取器,文件系统),使用画布的各种图像调整器转换器,使用base64images填充Web SQL数据库等等......但在这些情况下,您应该只创建一个函数目的...有时你需要一个blob,数组缓冲区,你可以设置标题,覆盖mimetype,还有更多......

但这里的问题是如何返回Ajax响应...(我添加了一个简单的方法.)

  • `2.ajax意味着异步..所以NO var res = x('url')..`这就是这个问题和答案的全部要点:)
  • 虽然这个答案很好(而且我们所有_love_ XHR2并且发布文件数据和多部分数据非常棒) - 这显示了使用JavaScript发布XHR的语法糖 - 你可能想把它放在博客文章中(我喜欢它)甚至在图书馆(不确定名称`x`,`ajax`或`xhr`可能更好:)).我没有看到它如何解决从AJAX调用返回响应的问题.(有人仍然可以做`var res = x("url")`并且不明白为什么它不起作用;)).另外一点 - 如果你从方法返回`c`这将是很酷的,所以用户可以挂钩'错误'等.
  • @cocco所以你在SO _answer_中编写了误导性的,不可读的代码,以便节省一些按键?请不要这样做.
  • 为什么函数中有一个'c'参数,如果在第一行你覆盖它有什么价值?我错过了什么吗?
  • 您可以使用参数作为占位符,以避免多次写入"var"

如果您使用承诺,这个答案适合您.

这意味着AngularJS,jQuery(带有延迟),本机XHR的替换(fetch),EmberJS,BackboneJS的保存或任何返回promises的节点库.

你的代码应该是这样的:

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的答案.这个答案是对前端或后端的承诺的一般用法.


核心问题

浏览器和具有NodeJS/io.js的服务器上的JavaScript并发模型是异步被动的.

无论何时调用返回promise的方法,then处理程序总是异步执行 - 也就是说,它们之下的代码之后不在.then处理程序中.

这意味着当您返回已定义datathen处理程序时尚未执行.这反过来意味着您返回的值未及时设置为正确的值.

以下是该问题的简单类比:

    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

的价值dataundefined因为data = 5部分尚未执行.它可能会在一秒钟内执行,但到那时它与返回的值无关.

由于操作尚未发生(AJAX,服务器调用,IO,计时器),因此在请求有机会告诉您的代码该值是什么之前,您将返回该值.

一个可能的解决这个问题的代码重新活跃,告诉你的程序在计算完成后做什么.承诺通过在时间上是时间敏感的(时间敏感的)来积极地实现这一点.

快速回顾承诺

承诺是一种随时间变化价值.承诺有状态,它们开始等待没有价值,可以解决:

  • 实现了计算成功完成的意义.
  • 拒绝意味着计算失败.

承诺只能改变一次状态,之后它将永远保持在同一状态.您可以将then处理程序附加到promises以提取其值并处理错误.then处理程序允许链接电话.Promise是通过使用返回它们的API创建的.例如,更现代的AJAX替代品fetch或jQuery的$.get回报承诺.

当我们召唤.then一个承诺并从中返回一些东西时 - 我们得到了对已处理值的承诺.如果我们回到另一个承诺,我们会得到惊人的东西,但让我们抓住我们的马.

有了承诺

让我们看看我们如何用promises解决上述问题.首先,让我们通过使用Promise构造函数来创建延迟函数来演示我们对上面的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转换为使用promises之后,我们可以使用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`
})

所以这也适用.我们已经知道我们不能从已经异步的调用中返回值,但是我们可以使用promises并将它们链接起来执行处理.我们现在知道如何从异步调用返回响应.

ES2015(ES6)

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.coroutine这里使用Bluebird ,但还有其他包装,如coQ.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();

ES2016(ES7)

在ES7中,这是进一步标准化的,现在有几个提案,但在所有提案中你都可以await保证.通过添加asyncawait关键字,这只是上面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
}

它仍然返回一个相同的承诺:)


您正在使用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);
}
});

在提交处理程序中返回任何内容都不会执行任何操作.您必须切换数据,或者直接在成功函数内执行您想要的操作.

  • 这个答案完全是语义的...你的成功方法只是回调中的回调.你可以只有`success:handleData`它会起作用.
  • 如果你想在"handleData"之外返回"responseData"怎么办... 🙂 ...你会怎么做......?...导致一个简单的返回将它返回到ajax的"成功"回调...而不是在"handleData"之外...
  • This answer is SO **clear and simple** and saved my (hours!). Thank you. Would upvote 100 times if I could.

最简单的解决方案是创建一个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);
});
  • 对不起,我忘记发表评论(我经常这样做!).我贬低了它.Downvotes并不表示事实的正确性或缺乏,它们表明在上下文中缺乏有用性.我没有找到你的答案,因为菲利克斯已经解释了这一点,但更详细.另外,如果它是JSON,为什么要对响应进行字符串化?
  • 好的.. @Benjamin我使用stringify,将JSON对象转换为字符串.并且谢谢你澄清你的观点.请记住发布更精细的答案.
  • 我不知道是谁投票否定的.但这是一个工作,事实上我使用这种方法来创建一个完整的应用程序.jquery.ajax不返回数据,因此最好使用上述方法.如果这是错的,请解释并建议更好的方法.

我会回答一个看起来很可怕的手绘漫画.第二图像是为什么的原因resultundefined在你的代码示例.

  • **一张图片胜过千言万语**,**人A** - 问问人员B细节修理他的车,反过来**人B** - 做Ajax电话并等待服务器响应汽车修理细节,当收到响应时,Ajax Success函数调用Person B函数并将响应作为参数传递给它,Person A收到答案.
  • 如果您为每个图像添加代码行以说明概念,那将会很棒.
  • Meanwhile, the guy with the car is stuck on the side of the road. He *requires* the car is fixed before continuing. He is now alone on the side of the road waiting... He would rather be on the phone waiting for status changes but the mechanic would not do it... The mechanic said he has to get on with his job and can't simply hang out on the phone. Mechanic promised he would call him back as soon as he could. After about 4 hours, the guy gives up and calls Uber. - Example of timeout.
  • @barrypicker 😀 Brilliant!

Angular1

对于使用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和Later

Angular2看下面的例子中,但建议使用ObservablesAngular2.

 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,如果你想使用它,你可能需要插件.

此外,这里是promises 规范定义.

  • 这并不能解释承诺如何解决这个问题.
  • 它不起作用.promiseB将获得'undefined'
  • jQuery和[fetch](https://github.com/github/fetch)方法都返回promises.我建议修改你的答案.虽然jQuery不完全相同(然后就是那里,但抓不到).

这里的大多数答案都提供了有关何时进行单个异步操作的有用建议,但有时,当您需要对数组中的每个条目或其他类似列表的结构执行异步操作时,会出现这种情况.诱惑是这样做:

// 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); // E.g., 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); // E.g., 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在呼叫突出时改变的可能性......)

注意我们如何使用indexfrom forEach将结果保存在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它做一个包装,返回一个承诺,然后做下面的...)

如果doSomethingAsync给你一个承诺,你可以使用Promise.all:

function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});

例:

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));
});
}

请注意,doSomethingAsync当它们全部被解析时,会使用您提供的所有承诺的结果数组来解析其承诺,或者当您给予它的第一个承诺拒绝时拒绝承诺.

系列

假设您不希望操作并行?如果要一个接一个地运行它们,则需要等到每个操作完成后再开始下一个操作.这是一个函数的示例,它执行该操作并使用结果调用回调:

.as-console-wrapper {
max-height: 100% !important;
}

(因为我们正在进行系列工作,所以我们可以使用,map因为我们知道我们不会不按顺序得到结果.在上面我们可以使用map,但在下面的一些例子中我们没有索引使用.)

例:

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);
});
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));
}

(或者,再次,为Promise.all它构建一个包装器给你一个承诺并做下面的...)

如果results.push(result)给你一个承诺,如果你可以使用ES2017 +语法(也许像transpiler 巴贝尔),您可以使用results[index] = result;函数与doSomethingAsyncdoSomethingAsync:

.as-console-wrapper {
max-height: 100% !important;
}

例:

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));
});
}

如果你还不能使用ES2017 +语法,你可以使用"Promise reduce"模式的变体(这比通常的Promise减少更复杂,因为我们没有将结果从一个传递到下一个,而是在数组中收集结果):

.as-console-wrapper {
max-height: 100% !important;
}

例:

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));
});
}

......使用ES2015 +箭头功能不那么麻烦:

.as-console-wrapper {
max-height: 100% !important;
}

例:

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));
});
}
  • 你能解释一下代码的`if (--expecting === 0)`部分是如何工作的吗?您的解决方案的回调版本对我来说非常有用,我只是不明白如何使用该语句检查已完成的响应数量。欣赏这只是我缺乏知识。有没有其他方法可以写支票?
  • @Sarah:`expecting` 以 `array.length` 的值开始,这是我们要发出的请求数。我们知道在所有这些请求开始之前不会调用回调。在回调中,`if (--expecting === 0)` 执行以下操作: 1. 递减 `expecting`(我们收到了一个响应,所以我们期望少一个响应)并且如果值 *after* the递减为 0(我们不期望有更多响应),我们完成了!

看看这个例子:

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);
});
})();

这是在许多新的JavaScript框架中使用的两种方式数据绑定对你来说很有用的地方之一......

因此,如果您正在使用Angular,React或任何其他两种方式进行数据绑定,那么这个问题只是为您修复,所以简单来说,您的结果是undefined在第一阶段,所以result = undefined在收到数据之前就已经得到了,然后,只要你得到结果,它就会更新并被分配给你的Ajax调用响应的新值......

但是你如何在纯javascriptjQuery中做到这一点,例如你在这个问题中提到的?

您可以使用回调,promise和最近的observable为您处理它,例如在promises中我们有一些函数,如success()或then(),它们将在您的数据准备就绪时执行,与回调或订阅函数相同在可观察的.

例如,在您使用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
});

有关promisesobservables的更多信息,这些是更新的方法来执行此异步操作.

  • 这实际上是不正确的,因为React是单向数据绑定
  • 这在全局范围内很好,但在某些模块上下文中,您可能希望确保回调的正确上下文,例如`$.ajax({url: "api/data", success: fooDone.bind(this)});`
  • @MatthewBrent 你没有错,但也不对,React props 是对象,如果更改,它们会在整个应用程序中更改,但这不是 React 开发人员推荐使用它的方式......

从异步函数返回值的另一种方法是传入将存储异步函数结果的对象.

这是一个相同的例子:

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对象在异步操作期间存储该值.这使得即使在异步作业之后结果也可用.

我经常使用这种方法.我很想知道这种方法在通过连续模块连接结果的过程中有多好.

  • 在这里使用对象没有什么特别之处.如果你直接对`result`做出回应,它也会有效.它的工作原理是因为你在异步函数完成后读取变量*.

虽然承诺和回调在许多情况下都能很好地发挥作用,但在后面表达类似的东西是痛苦的:

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
}

您可以在此处查看项目.

  • @recurf - 这不是我的项目。您可以尝试使用他们的问题跟踪器。
  • 这类似于生成器函数吗?https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  • 这仍然相关吗?
  • 如果您使用的是一些最新版本的 node,则可以使用 `async-await`。如果有人坚持使用旧版本,他们可以使用此方法。

我写的以下示例显示了如何操作

  • 处理异步HTTP调用;
  • 等待每个API调用的响应;
  • 使用Promise模式;
  • 使用Promise.all模式加入多个HTTP调用;

这个工作示例是独立的.它将定义一个使用window XMLHttpRequest对象进行调用的简单请求对象.它将定义一个简单的函数来等待一堆承诺完成.

语境.该示例是查询Spotify Web API端点,以便搜索playlist给定查询字符串集的对象:

[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]

对于每个项目,新的Promise将触发一个块 - ExecutionBlock解析结果,根据结果数组调度一组新的promise,即一个Spotify user对象列表,并在ExecutionProfileBlock异步中执行新的HTTP调用.

然后,您可以看到嵌套的Promise结构,它允许您生成多个完全异步的嵌套HTTP调用,并加入来自每个调用子集的结果Promise.all.

注意
最近的Spotify searchAPI将要求在请求标头中指定访问令牌:

-H "Authorization: Bearer {your access token}"

因此,您需要运行以下示例,您需要将访问令牌放在请求标头中:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// 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);
});
<div />

我在这里广泛讨论了这个解决方案.


简而言之,你必须实现这样的回调:

function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});

2017答案:您现在可以在每个当前浏览器和节点中完全按照您的要求进行操作

这很简单:

  • 回报承诺
  • 使用'await',它将告诉JavaScript等待将其解析为值的承诺(如HTTP响应)
  • 将'async'关键字添加到父函数

这是您的代码的工作版本:

(async function(){
var response = await superagent.get('...')
console.log(response)
})()

所有当前浏览器和节点8都支持await

  • 不幸的是,这仅适用于返回promises的函数 - 例如,它不适用于使用回调的Node.js API.我不建议在没有Babel的情况下使用它,因为不是每个人都使用"当前的浏览器".
  • @MichałPerłakowski节点8包括https://nodejs.org/api/util.html#util_util_promisify_original,可用于使node.js API返回promise.您是否有时间和金钱来支持非当前浏览器显然取决于您的情况.

在与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),但它确实启动了一个计时器,它在1s后执行一个函数来返回'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的调用等,您可以使用resolve对值的承诺(您打算返回).因此,简而言之,您在异步函数中解析值而不是返回值.

UPDATE(与异步/等待的承诺)

除了使用then/catch承诺之外,还有一种方法.我们的想法是识别异步函数,然后在转移到下一行代码之前等待promises解析.它仍然只是promises引擎盖下,但采用了不同的语法方法.为了使事情更清楚,您可以在下面找到比较:

然后/捕获版本:

function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
throw err;
})
}

async/await版本:

  async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
throw err;
}
}
  • @edwardsmarkf 我个人认为没有最好的方法。我将 promise 与 then/catch 、 async/await 以及我的代码异步部分的生成器一起使用。这在很大程度上取决于使用的上下文。

您可以使用此自定义库(使用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)
});

另一种解决方案是通过顺序执行器nsynjs执行代码.

如果潜在的功能被宣传

nsynjs将按顺序评估所有promise,并将promise结果放入dataproperty:

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');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

如果基础功能没有被承诺

步骤1.使用回调函数将函数包装到nsynjs-aware包装器中(如果它具有promisified版本,则可以跳过此测试):

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.通过nnsynjs以同步方式运行函数:

nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});

Nsynjs将逐步评估所有运算符和表达式,暂停执行,以防某些慢速函数的结果未准备就绪.

更多示例:https://github.com/amaksr/nsynjs/tree/master/examples

  • 这是有趣的.我喜欢它如何允许以您在其他语言中的方式编写异步调用.但从技术上讲,它不是真正的JavaScript?

浏览器可以分为三个部分:

1)事件循环

2)Web API

3)事件队列

Event Loop永远运行,即无限循环.Event Queue是在某些事件上推送所有函数的地方(例如:click)这是逐个执行队列并放入Event循环执行此函数并准备自己执行第一个函数之后的下一个函数.这意味着在事件循环中执行队列之前的函数之前,不会启动一个函数的执行.

现在让我们认为我们在队列中推送了两个函数,一个用于从服务器获取数据,另一个用于利用该数据.我们先将队列中的serverRequest()函数推送到utiliseData()函数.serverRequest函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据需要多长时间,所以这个过程需要花费时间,所以我们忙着我们的事件循环因此挂起我们的页面,那就是Web API发挥作用它从事件循环中获取此函数并处理服务器使事件循环空闲,以便我们可以从队列执行下一个函数.队列中的下一个函数是utiliseData(),它循环但由于没有数据可用它去浪费和下一个函数的执行一直持续到队列结束.(这称为异步调用,即我们可以做其他事情,直到我们得到数据)

假设我们的serverRequest()函数在代码中有一个return语句,当我们从服务器获取数据时,Web API将在队列末尾将其推送到队列中.由于它在队列末端被推送,我们无法利用其数据,因为我们的队列中没有剩余的功能来利用这些数据.因此无法从异步调用返回任何内容.

因此,对此的解决方案是回调承诺.

来自其中一个答案的图像,正确解释回调使用...
我们将功能(利用服务器返回的数据的功能)提供给功能调用服务器.

 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在顶部回答)
/sf/answers/995422641/


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或closure-compiler运行代码来生成ECMAScript 5.

回调...args被包裹在一个数组,当你阅读这些解构使得图案可以有多个参数回调应付.例如,使用节点fs:

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
  • 我很想知道为什么这会被低估.这似乎是一个以前没有给出的简单答案.

以下是一些使用异步请求的方法:

  1. 浏览器承诺对象
  2. Q - JavaScript的承诺库
  3. A + Promises.js
  4. jQuery推迟了
  5. XMLHttpRequest API
  6. 使用回调概念 - 作为第一个答案中的实现

示例:jQuery延迟实现以处理多个请求

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()方法立即返回,而在函数返回后$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有没有办法知道它需要等待order_milk完成它执行之前put_in_coffee.换句话说,它不知道这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放入牛奶后执行的action().这样的代码变得难以编写,读取和调试.

在这种情况下,我们可以将问题中的代码重写为:

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');
}

实际上,我们所做的就是添加一个return来调用$.ajax.这是有效的,因为jQuery $.ajax已经返回了一种类似承诺的东西.(在实践中,没有详细介绍,我们宁愿包装这个调用,以便返回一个真正的承诺,或者使用一些替代方法$.ajax.)现在,如果我们想加载文件并等待它完成,然后做点什么,我们可以简单地说

get_data() . then(do_something)

例如,

get_data() .
then(function(data) { console.log(data); });

使用promises时,我们最终会传递大量函数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);
}

callback()foo()成功中使用一个功能.试试这种方式.它简单易懂.  

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();

当然有许多方法,如同步请求,承诺,但根据我的经验,我认为你应该使用回调方法.Javascript的异步行为很自然.因此,您的代码段可以重写一点:

function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
  • 关于回调或JavaScript,没有什么本质上是异步的.

问题是:

可以解释为:

解决方案是避免回调,并使用Promisesasync/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")

在阅读了这里的所有回复和我的经验后,我想继续介绍callback, promise and async/awaitJavaScript 中异步编程的细节。

1) 回调:回调的根本原因是为了响应一个事件而运行代码(见下面的例子)。我们每次都在 JavaScript 中使用回调。

const body = document.getElementsByTagName('body')[0];
function callback() {
console.log('Hello');
}
body.addEventListener('click', callback);

但是如果你必须在下面的例子中使用许多嵌套的回调,那么代码重构将非常糟糕。

asyncCallOne(function callback1() {
asyncCallTwo(function callback2() {
asyncCallThree(function callback3() {
...
})
})
})

2) Promise:一种 ES6 语法 - Promise 解决了回调地狱问题!

const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR request or an HTML5 API.
setTimeout(() => {
resolve("Success!")  // Yay! Everything went well!
}, 250)
})
myFirstPromise
.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
})
.catch((e) => {
console.log(e);
});

myFirstPromise 是一个代表异步代码过程的 Promise 实例。resolve 函数表示 Promise 实例已经完成。之后,我们可以在 promise 实例上调用 .then() (如您所愿的 .then 链)和 .catch() :

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async/Await: ES6 的新语法 - Await 基本上是 Promise 的语法糖!

Async 函数为我们提供了一种干净简洁的语法,使我们能够编写更少的代码来实现与 promise 相同的结果。Async/Await 看起来类似于同步代码,同步代码更容易阅读和编写。为了捕捉 Async/Await 的错误,我们可以使用 block try...catch。在这里,您不需要编写 Promise 语法的 .then() 链。

const getExchangeRate = async () => {
try {
const res = await fetch('https://getExchangeRateData');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
getExchangeRate();

使用承诺

这个问题最完美的答案就是使用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
});

我们为什么要使用自己的自定义Promise?

我使用此解决方案已有一段时间,直到我发现旧浏览器中存在错误:

Uncaught ReferenceError: Promise is not defined

所以如果没有定义,我决定将ES3的Promise类实现 js编译器之上.

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;
}();
}

1. 第一步

至于其他许多人,一开始我遇到的异步调用令人费解。
我不记得细节,但我可能尝试过类似的事情:

let result;
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos/1',
success: function (response) {
console.log('\nInside $.ajax:');
console.log(response);
result = response;
}
});
console.log('Finally, the result: ' + result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

哎呀!console.log('Finally, the result: ' + result);
我认为最后打印的行的输出
实际上是其他输出之前打印的!– 并且它不包含结果:它只是打印undefined.
1
怎么来的?

一个有用的见解

我清楚地记得我的第一次啊哈!关于如何理解异步调用的时刻。
正是这条评论说:
您实际上不想回调中获取数据;
您想将需要数据的操作放入回调中!

2
这在上面的例子中很明显。
但是是否仍然可以在异步调用完成编写处理响应的代码?

2. 纯 JavaScript 和回调函数

答案是肯定的!- 有可能的。
一种替代方法是在延续传递风格中使用回调函数:
3

const url = 'https://jsonplaceholder.typicode.com/todos/2';
function asynchronousCall (callback) {
const request = new XMLHttpRequest();
request.open('GET', url);
request.send();
request.onload = function () {
if (request.readyState === request.DONE) {
console.log('The request is done. Now calling back.');
callback(request.responseText);
}
};
}
asynchronousCall(function (result) {
console.log('This is the start of the callback function. Result:');
console.log(result);
console.log('The callback function finishes on this line. THE END!');
});
console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper { max-height: 100% !important; top: 0; }

注意函数asynchronousCall是怎样的void。它什么都不返回。相反,通过asynchronousCall使用匿名回调函数 ( asynchronousCall(function (result) {...)调用,该函数对结果执行所需的操作,但仅请求完成后 - 当responseText可用时。

运行上面的代码片段显示了我可能不想在异步调用之后编写任何代码
(例如 line
LAST in the code, but executed FIRST!)。
为什么?– 因为这样的代码会异步调用传递任何响应数据之前发生。
这样做必然会在将代码输出进行比较时造成混淆。

3. 承诺.then()– 或async/await

.then()结构是在引进ECMA-262第6版2015年6月,和async/await构建所引入的ECMA-262第8版于2017年6月
下面的代码仍然是普通的JavaScript,取代了老派
的XMLHttpRequest获取
4

fetch('http://api.icndb.com/jokes/random')
.then(response => response.json())
.then(responseBody => {
console.log('.then() - the response body:');
console.log(JSON.stringify(responseBody) + '\n\n');
});
async function receiveAndAwaitPromise () {
const responseBody =
(await fetch('http://api.icndb.com/jokes/random')).json();
console.log('async/await:');
console.log(JSON.stringify(await responseBody) + '\n\n');
}
receiveAndAwaitPromise();
.as-console-wrapper { max-height: 100% !important; top: 0; }

如果您决定使用async/await
构造,则需要警告一下。请注意上面的代码片段await中的两个地方需要如何。如果一开始就忘记了,就不会有输出。如果第二次忘记,唯一的输出将是空对象,{}
(或[object Object][object Promise])。
忘记async函数的前缀可能是最糟糕的——输出将是"SyntaxError: missing ) in parenthetical"——没有提到丢失的 async关键字。

4. Promise.all – URL 数组5

假设我们需要请求一大堆 URL。我可以发送一个请求,等待它响应,然后发送下一个请求,等待响应,等等......
啊!– 这可能需要很长时间。那岂不是更好,如果我可以把他们全部一次,然后等待时间不超过所花费到达最慢的响应?

作为一个简化的例子,我将使用:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3']

两个 URL 的 JSON:

{"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
"completed":false}
{"userId":1,"id":3,"title":"fugiat veniam minus","completed":false}

目标是获取一个对象数组,其中每个对象都包含title
来自相应 URL的值。

为了让它更有趣,我假设已经有一个名称数组,我希望 URL 结果数组(titles)与其合并:

namesonly = ['two', 'three']

所需的输出是一个 mashup 组合namesonly并组合urls成一个
对象数组

[{"name":"two","loremipsum":"quis ut nam facilis et officia qui"},
{"name":"three","loremipsum":"fugiat veniam minus"}]

我已将名称更改titleloremipsum.

const namesonly = ['two','three'];
const urls = ['https://jsonplaceholder.typicode.com/todos/2',
'https://jsonplaceholder.typicode.com/todos/3'];
Promise.all(urls.map(url => fetch(url)
.then(response => response.json())
.then(responseBody => responseBody.title)))
.then(titles => {
const names = namesonly.map(value => ({ name: value }));
console.log('names: ' + JSON.stringify(names));
const latins = titles.map(value => ({ loremipsum: value }));
console.log('latins:\n' + JSON.stringify(latins));
const result =
names.map((item, i) => Object.assign({}, item, latins[i]));
console.log('result:\n' + JSON.stringify(result));
});
.as-console-wrapper { max-height: 100% !important; top: 0; }

上面的所有示例都很简短,简洁地传达了异步调用如何在小玩意的 API 上使用。使用小型 API 可以很好地解释概念和工作代码,但这些示例可能是一些空运行。

下一节将展示一个更现实的示例,说明如何组合 API 以创建更有趣的输出。

5. 如何在 Postman 6 中可视化混搭

MusicBrainz API
包含有关艺术家和乐队的信息。
一个例子——对英国摇滚乐队酷玩乐队的请求是:
http : //musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234 ?& fmt=json&inc=url-rels+release- groups。
JSON 响应包含乐队最早的 25 个专辑名称等。此信息在release-groups数组中。这个数组的开头,包括它的第一个对象是:

...
"release-groups": [
{
"id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
"secondary-type-ids": [],
"first-release-date": "2000-07-10",
"primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
"disambiguation": "",
"secondary-types": [],
"title": "Parachutes",
"primary-type": "Album"
},
...

这个 JSON 片段显示酷玩乐队的第一张专辑是Parachutesid在本例中1dc4c347-a1db-32aa-b14f-bc9cc507b843,它还给出了,它是专辑的唯一标识符。

此标识符可用于在封面艺术档案 API 中进行查找:
http : //coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843。
7

对于每张专辑,JSON 响应包含一些图像,其中一张是专辑的封面。对上述请求的响应的前几行:

{
"images": [
{
"approved": true,
"back": false,
"comment": "",
"edit": 22132705,
"front": true,
"id": 4086974851,
"image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
"thumbnails": {
"250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
"500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
"1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
"large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
},
...

有趣的是这里的线
"small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
该 URL 是指向Parachutes专辑封面的直接链接。

创建和可视化混搭的代码

总体任务是使用 Postman 可视化一个乐队的所有专辑标题和封面。在如何在 Postman 中可视化 API mashup的问题
的回答中已经相当详细地描述了如何编写代码来实现这一点 – 因此,我将在这里避免冗长的讨论,只提供代码和结果的屏幕截图:

const lock = setTimeout(() => {}, 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => {
const bandName = responseO.json().name;
const albums = responseO.json()['release-groups'];
for (const item of albums) {
albumsArray.push(item.title);
urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
}
albumsArray.length = urlsArray.length = 15;
const images = [];
let countDown = urlsArray.length;
urlsArray.forEach((url, index) => {
asynchronousCall(url, imageURL => {
images[index] = imageURL;
if (--countDown === 0) { // Callback for ALL starts on next line.
clearTimeout(lock); // Unlock the timeout.
const albumTitles = albumsArray.map(value => ({ title: value }));
const albumImages = images.map(value => ({ image: value }));
const albumsAndImages = albumTitles.map(
(item, i) => Object.assign({}, item, albumImages[i]));
const template = `<table>
<tr><th>` + bandName + `</th></tr>
{{#each responseI}}
<tr><td>{{title}}<br><img src="https://qa.1r1g.com/sf/ask/995422501/{{image}}"></td></tr>
{{/each}}
</table>`;
pm.visualizer.set(template, { responseI: albumsAndImages });
}
});
});
function asynchronousCall (url, callback) {
pm.sendRequest(url, (_, responseI) => {
callback(responseI.json().images.find(obj => obj.front === true)
.thumbnails.small); // Individual callback.
});
}
});

结果和文件

如何下载和运行 Postman Collection

运行 Postman Collection 应该很简单。
假设您使用的是桌面版 Postman,请执行以下操作:

参考

  • 如何从异步调用返回响应?
  • 关于异步调用的一些问答
  • 使用纯 JavaScript 和回调函数
  • 连续传球风格
  • XMLHttpRequest:onload 与 onreadystatechange
  • XMLHttpRequest.responseText
  • 示例演示async/await
  • 拿来
  • 承诺
  • XMLHttpRequest 标准
  • 获取标准
  • Web 超文本应用技术工作组 (WHATWG)
  • ECMA 规范的链接
  • 将值数组转换为对象数组
  • 如何使用 Promise.all 获取 URL 数组?
  • MusicBrainz API 的文档
  • 封面艺术档案 API 的文档
  • 如何在 Postman 中可视化 API 混搭?

1原发帖人表示:他们都回来了
undefined

2如果您认为异步调用令人困惑,请考虑查看有关异步调用的一些问题和答案,看看是否有帮助。
3XMLHttpRequest是误导性的X
AJAX -这几天的Web API的数据格式是无处不在的JSON,而不是XML。
4 Fetch
返回一个Promise。我很惊讶地发现XMLHttpRequestFetch都不是ECMAScript 的一部分标准。JavaScript 可以在这里访问它们的原因是 Web 浏览器提供了它们。
Fetch 标准和
XMLHttpRequest 标准均由
2004 年 6 月成立的 Web 超文本应用技术工作组 (WHATWG) 支持。
5本节借鉴了
如何使用 Promise.all 获取 URL 数组?.
6本节在很大程度上依赖于
如何在 Postman 中可视化 API 混搭?.
7此 URL 会自动重定向到:https :
//ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json。
8如果出现错误,
运行脚本时出现问题,请尝试再次点击发送


而不是向你抛出代码,有两个概念是理解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继续执行队列中的下一个项目,同时等待消息被添加回队列.

这就是为什么使用异步功能我们使用称为回调的东西.这有点像一个字面上的承诺.正如我承诺在某些时候返回一些东西, jQuery使用调用的特定回调deffered.done deffered.faildeffered.always(以及其他).你可以在这里看到它们

所以你需要做的是传递一个承诺在某个时刻执行的函数,并传递给它的数据.

因为回调不是立即执行,而是在以后执行,所以将引用传递给函数并不重要.所以

function foo(bla) {
console.log(bla)
}

所以大部分的时间(但不总是),你会通过foofoo()

希望这会有所帮助.当你遇到这样的事情似乎令人困惑时 - 我强烈建议你完全阅读文档,至少要了解它.它会让你成为一个更好的开发者.


使用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)
})

在看树之前先看看森林.

这里有许多信息丰富的答案和细节,我不会重复其中任何一个.在JavaScript中编程的关键是首先要有正确的整体执行心理模型.

  1. 您的入口点将作为事件的结果执行.例如,带有代码的脚本标记将加载到浏览器中.(因此,这就是为什么您可能需要关注页面是否准备好运行代码,如果它需要首先构造dom元素,等等)
  2. 您的代码执行完成 - 无论它做多少异步调用 - 都不执行任何回调,包括XHR请求,设置超时,dom事件处理程序等.等待执行的每个回调都将位于队列中,等待他们轮到在其他被解雇的事件完成执行后运行.
  3. 一旦调用,每个单独的回调到XHR请求,设置超时或dom事件将运行完成.

好消息是,如果你理解这一点,你将永远不必担心竞争条件.首先,您应该如何组织代码本质上是对不同离散事件的响应,以及您希望如何将它们组合成逻辑序列.您可以使用promises或更高级别的新异步/等待作为工具,或者您可以自己动手.

但是,在熟悉实际问题域之前,不应使用任何战术工具来解决问题.绘制这些依赖关系的映射,以了解何时需要运行.尝试对所有这些回调采用临时方法并不能很好地为您服务.


您无法直接从函数返回 Ajax 响应的结果。原因是 Ajax 调用($.get()$.post())是异步的,并且调用封装 Ajax 调用的函数甚至会在呈现响应之前返回。

在这种情况下,唯一的选择是返回一个 promise 对象,在响应到达时进行解析。

有两种方法可以解决上述问题。两者都使用承诺。

下面的代码片段包含一个 JSON URL。两者都可以工作并且可以直接复制到JSFiddle并进行测试。

选项#1 - 直接从 foo 方法返回 Ajax 调用。
在最新版本的 jQuery 中,Ajax 调用会返回一个 promise 对象,可以使用.then函数来解析该对象。在代码中,在这种情况下,.then函数前面是要解析的回调函数foo()

   // Declare function foo
function foo(url)
{
return $.get(url);
}
// Invoke the foo function, which returns a promise object
// the 'then' function accepts the call back to the resolve function
foo('https://jsonplaceholder.typicode.com/todos/1')
.then(function(response)
{
console.log(response);
})
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

选项#2 - 声明一个承诺对象并返回它。
在函数内声明一个promise 对象,在该promise 函数中封装Ajax 调用并返回promise 对象。

   function foo1() {
var promise = new Promise(function(resolve, reject)
{
$.ajax({
url: 'https://jsonplaceholder.typicode.com/todos/1',
success: function(response) {
console.log(response);
resolve(response);
// return response; // <- I tried that one as well
}
});
});
return promise;
}
foo1()
.then(function(response)
{
console.log('Promise resolved:');
console.log(response);
})
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

这是一个有效的例子:

const validateName = async userName => {
const url = "abc/xyz";
try {
const response = await axios.get(url);
return response.data
} catch (err) {
return false;
}
};
validateName("user")
.then(data => console.log(data))
.catch(reason => console.log(reason.message))

我认为无论使用什么方法或机制,或者是什么框架(Angular/React)将它隐藏起来,以下原则都成立:


最初,回调用于异步操作(例如,在XMLHttpRequest API 中)。现在,像浏览器的Fetch API这样的基于Promise 的 API已经成为默认解决方案,并且async/await所有现代浏览器和 Node.js(服务器端)都支持更好的语法。

一个常见的场景 - 从服务器获取 JSON 数据 - 可能如下所示:

async function fetchResource(url) {
const res = await fetch(url);
if (!res.ok) {
throw new Error(res.statusText);
}
return res.json();
}

在另一个函数中使用它:

async function doSomething() {
try {
const data = await fetchResource("https://example.test/resource/1");
// ...
} catch (e) {
// Handle error
...
}
}

如果您设计现代 API,强烈建议您更喜欢基于 Promise 的风格而不是回调。如果您继承了依赖回调的 API,则可以将其包装为 promise:

function sleep(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
async function fetchAfterTwoSeconds(url) {
await sleep(2000);
return fetchResource(url);
}

在历史上完全依赖回调的 Node.js 中,这种技术非常普遍,以至于他们添加了一个名为util.promisify.


请求以异步方式工作,因此您不能像典型代码一样以同步方式读取数据。但是,使用async/await可以创建看起来与同步代码相似的异步代码。需要处理请求数据的代码需要由async函数包装(load在下面的代码段中),并且在其中需要添加awaitkeywort之前foo()(也要使用async/await)。

async function foo() {
var url= 'https://jsonplaceholder.typicode.com/todos/1';
var result= await (await fetch(url)).text(); // or .json()
return result;
}
async function load() {
var result = await foo();
console.log(result);
}
load();

以上是如何从异步调用返回响应?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>