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

如何从发出异步请求的函数foo返回响应/结果?

我试图从回调中返回值,以及将结果分配给函数内部的局部变量并返回那个,但这些方法都没有实际返回响应-它们都返回undefined或变量result的初始值。

接受回调的异步函数示例(使用jQuery的ajax函数):

function foo() {var result;
$.ajax({url: '...',success: function(response) {result = response;// return response; // <- I tried that one as well}});
return result; // It always returns `undefined`}

使用Node.js示例:

function foo() {var result;
fs.readFile("path/to/file", function(err, data) {result = data;// return data; // <- I tried that one as well});
return result; // It always returns `undefined`}

使用Promise的then块的示例:

function foo() {var result;
fetch(url).then(function(response) {result = response;// return response; // <- I tried that one as well});
return result; // It always returns `undefined`}
2010637 次浏览

有关不同示例的异步行为的更一般解释,请参阅为什么我的变量在函数内部修改后没有改变?-异步代码引用

→如果您已经了解问题,请跳到下面的可能解决方案。

问题

Ajax中的一个代表异步。这意味着发送请求(或者更确切地说接收响应)被排除在正常的执行流程之外。在你的例子中,$.ajax立即返回,下一条语句return result;在你作为success回调传递的函数被调用之前执行。

这里有一个类比,希望能让同步流和异步流之间的区别更清楚:

同步

想象一下,你给一个朋友打电话,让他帮你查点东西,虽然可能需要一段时间,但你还是在电话里等着,凝视着前方,直到你的朋友给你需要的答案。

当你进行包含“正常”代码的函数调用时,也会发生同样的事情:

function findItem() {var item;while(item_not_found) {// search}return item;}
var item = findItem();
// Do something with itemdoSomethingElse();

即使findItem可能需要很长时间才能执行,但在var item = findItem();之后的任何代码都必须等待,直到函数返回结果。

异步

你又因为同样的原因打电话给你的朋友。但是这次你告诉他你很着急,他应该打你的手机。你挂断电话,离开家,做你打算做的任何事情。一旦你的朋友给你回电话,你就在处理他给你的信息。

这正是您执行Ajax请求时发生的事情。

findItem(function(item) {// Do something with the item});doSomethingElse();

不是等待响应,而是立即继续执行并执行Ajax调用后的语句。为了最终获得响应,您提供了一个在收到响应后要调用的函数,一个回调(注意到什么吗?回电?)。调用之后的任何语句都会在调用回调之前执行。


解决方案

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

你问我为什么不好?

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

所有这些都会导致非常糟糕的用户体验。用户将无法判断一切是否正常。此外,对于连接缓慢的用户来说,效果会更糟。

在下文中,我们将研究三种不同的解决方案,它们都是建立在彼此之上的:

  • #0的承诺(ES2017+,如果您使用转译器或再生器,则在旧浏览器中可用)
  • 回调(在节点中流行)
  • #0的承诺(ES2015+,如果您使用众多Promise库之一,则可在较旧的浏览器中使用)

这三个都在当前浏览器和Node 7+中可用。


ES2017+:#0的承诺

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

async/await建立在Promise之上:async函数始终返回Promise。await“解开”Promise并导致Promise解析的值,或者在Promise被拒绝时抛出错误。

重要提示:您只能在async函数或javascript模块中使用await。模块之外不支持顶级await,因此如果不使用模块,您可能必须创建一个异步IIFE(立即调用函数表达式)来启动async上下文。

您可以在MDN上阅读有关#0#1的更多信息。

下面是一个详细说明上面延迟函数findItem()的示例:

// Using 'superagent' which will return a promise.var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promisefunction delay() {// `delay` returns a promisereturn new Promise(function(resolve, reject) {// Only `delay` is able to resolve or reject the promisesetTimeout(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 uservar bookIDs = await superagent.get('/user/books');// wait for 3 seconds (just for the sake of this example)await delay();// GET information about each bookreturn superagent.get('/books/ids='+JSON.stringify(bookIDs));} catch(error) {// If any of the awaited promises was rejected, this catch block// would catch the rejection reasonreturn null;}}
// Start an IIFE to use `await` at the top level(async function(){let books = await getAllBooks();console.log(books);})();

当前的浏览器节点版本支持async/await。您还可以通过在再生器(或使用再生器的工具,例如巴别塔)的帮助下将代码转换为ES5来支持旧环境。


让函数接受回调

回调是将函数1传递给函数2。函数2可以在准备好时调用函数1。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果会传递给回调。

在问题的示例中,您可以让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});}

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

你也可以在将响应传递给回调之前处理它:

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

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


ES2015+:然后()的承诺

Promiseapi是ECMAScript 6(ES2015)的新特性,但它已经有了很好的浏览器支持。还有许多库实现了标准的Promises API,并提供了其他方法来简化异步函数的使用和组合(例如蓝鸟)。

Promises是未来值的容器。当Promises收到值(解决)或被取消(拒绝)时,它会通知所有想要访问此值的“侦听器”。

与普通回调相比,它们的优势在于允许您解耦代码并且更容易组合。

下面是一个使用Promise的例子:

function delay() {// `delay` returns a promisereturn new Promise(function(resolve, reject) {// Only `delay` is able to resolve or reject the promisesetTimeout(function() {resolve(42); // After 3 seconds, resolve the promise with value 42}, 3000);});}
delay().then(function(v) { // `delay` returns a promiseconsole.log(v); // Log the value once it is resolved}).catch(function(v) {// Or do something else if it is rejected// (it would not happen in this example, since `reject` is not called).});
.as-console-wrapper { max-height: 100% !important; top: 0; }

Applied to our Ajax call we could use promises like this:

function ajax(url) {return new Promise(function(resolve, reject) {var xhr = new XMLHttpRequest();xhr.onload = function() {resolve(this.responseText);};xhr.onerror = reject;xhr.open('GET', url);xhr.send();});}
ajax("https://jsonplaceholder.typicode.com/todos/1").then(function(result) {console.log(result); // Code depending on result}).catch(function() {// An error occurred});
.as-console-wrapper { max-height: 100% !important; top: 0; }

Describing all the advantages that promise offer is beyond the scope of this answer, but if you write new code, you should seriously consider them. They provide a great abstraction and separation of your code.

More information about promises: HTML5 rocks - JavaScript Promises.

Side note: jQuery's deferred objects

Deferred objects are jQuery's custom implementation of promises (before the Promise API was standardized). They behave almost like promises but expose a slightly different API.

Every Ajax method of jQuery already returns a "deferred object" (actually a promise of a deferred object) which you can just return from your function:

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

附注:承诺陷阱

请记住,Promise和延迟对象只是未来值的容器,它们不是值本身。例如,假设你有以下内容:

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()在检查服务器上的“/密码”页面时不会冻结代码——它向服务器发送一个请求,在等待时,它会立即返回一个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

如果您直接使用#0对象,请将false作为第三个参数传递给#2

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本质上总是异步的(甚至不考虑此选项的另一个原因)。

如果您在代码中使用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的人提供替代方案。

注意,对于那些使用新的#0 API,Angular或Promise的人,我在下面添加了另一个答案


你所面对的

这是另一个答案中“问题解释”的简短摘要,如果您在阅读后不确定,请阅读。

AJAX中的一个代表异步。这意味着发送请求(或者更确切地说接收响应)被排除在正常的执行流程之外。在您的示例中,#0立即返回,下一条语句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 thisalert(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,不要这样做! Felix的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。这是MDN关于原因的另一个简短摘要:

XMLHttpRequest支持同步和异步通信。然而,一般来说,出于性能原因,异步请求应该优先于同步请求。

简而言之,同步请求会阻塞代码的执行……这可能会导致严重的问题……

如果你这样做,你可以传递一个标志。这里是如何

var request = new XMLHttpRequest();request.open('GET', 'yourURL', false);  // `false` makes the request synchronousrequest.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 loadedcallback(httpRequest.responseText);// We're calling our method};httpRequest.open('GET', "/echo/json");httpRequest.send();}

(小提琴)

我们现在已经让我们的foo函数接受一个在AJAX成功完成时运行的操作。我们可以通过检查响应状态是否不是200并相应地操作(创建一个故障处理程序等)来进一步扩展这一点。它有效地解决了我们的问题。

如果你仍然很难理解这一点,MDN的阅读AJAX入门指南

XMLHttpRequest(首先,阅读Benjamin GruenbaumFelixKling的答案)

如果您不使用jQuery并且想要一个在现代浏览器和移动浏览器中都能使用的简短的XMLHttpRequest 2,我建议这样使用它:

function ajax(a, b, c){ // URL, callback, just a placeholderc = new XMLHttpRequest;c.open('GET', a);c.onload = b;c.send()}

如你所见:

  1. 它比列出的所有其他功能都要短。
  2. 回调是直接设置的(所以没有额外的不必要的闭包)。
  3. 它使用新的onload(所以你不必检查readystate&&state)
  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},placeholderc = new XMLHttpRequest;c.open(e||'get', a);c.onload = b;c.send(d||null)}

这是一个非常短的函数,但它执行get和POST。

使用示例:

x(url, callback); // By default it's GET so no need to setx(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}, placeholderc = new XMLHttpRequest;c.open(e||'get', a);c.onload = b;c.onerror = error;c.send(d||null)}
function error(e){console.log('--Error--', this.type);console.log('this: ', this);console.log('Event: ', e)}function displayAjax(e){console.log(e, this);}x('WRONGURL', displayAjax);

在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会损害函数。错误处理程序也可用于其他函数。

但要真正解决错误,只有方法是编写错误的URL,在这种情况下,每个浏览器都会抛出错误。

如果您设置自定义标头、将响应类型设置为blob数组缓冲区或其他…

即使你传递'POSTAPAPAP'作为方法,它也不会抛出错误。

即使你将'fdggdgilfdghfldj'作为formdata传递,它也不会抛出错误。

在第一种情况下,错误在this.statusText下的displayAjax()内作为Method not Allowed

在第二种情况下,它只是工作。您必须在服务器端检查是否传递了正确的帖子数据。

跨域不允许自动抛出错误。

在错误响应中,没有任何错误代码。

只有this.type被设置为错误

如果您完全无法控制错误,为什么要添加错误处理程序?大多数错误都在回调函数displayAjax()中返回。

所以:如果您能够正确复制和粘贴URL,则不需要进行错误检查。;)

PS:作为第一个测试,我写了x('x', displayAjax)…,它完全得到了响应…???所以我检查了超文本标记语言所在的文件夹,有一个名为'x.xml'的文件。所以即使你忘记了文件的扩展名XMLHttpRequest 2也会找到它.我笑了


同步读取文件

不要那样做。

如果您想暂时阻止浏览器,请同步加载一个漂亮的大.txt文件。

function omg(a, c){ // URLc = 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,我集成到每个超文本标记语言页面的第一个函数之一是这个答案中的第一个Ajax函数,只有GET。

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

我做了一个下载管理器(使用恢复,文件阅读器和文件系统两侧的范围),使用画布的各种图像调整器转换器,用Base64图像填充WebSQL数据库等等。

但在这些情况下,你应该为此目的创建一个函数……有时你需要一个blob、数组缓冲区、你可以设置标头、覆盖mimetype,还有更多……

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

最简单的解决方案是创建一个JavaScript函数并调用它来进行Ajaxsuccess回调。

function callServerAsync(){$.ajax({url: '...',success: function(response) {
successCallback(response);}});}
function successCallback(responseObj){// Do something like read the response and show dataalert(JSON.stringify(responseObj)); // Only applicable to a 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 dataconsole.log(responseData);}
$.ajax({url: "hi.php",...success: function ( data, status, XHR ) {handleData(data);}});

在提交处理程序中返回任何内容都不会做任何事情。您必须要么交出数据,要么直接在成功函数中对其进行您想要的操作。

角1

使用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.

Angular 2及以后

在Angular 2中,看看下面的例子,但它的建议使用可观察与Angular 2。

 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规范

如果你正在使用Promise,这个答案是给你的。

这意味着AngularJS,jQuery(延迟),原生XHR的替换(获取),Ember.jsBackbone.js的保存或任何返回Promise的Node.js库。

你的代码应该是这样的:

function foo() {var data;// Or $.get(...).then, or request(...).then, or query(...).thenfetch("/echo/json").then(function(response){data = response.json();});return data;}
var result = foo(); // 'result' is always undefined no matter what.

Felix Kling干得不错为使用带有Ajax回调的jQuery的人编写答案。我有一个原生XHR的答案。这个答案适用于前端或后端的承诺的通用用法。


核心问题

浏览器和服务器上的JavaScript并发模型Node.js/io.js是异步反应

每当您调用返回Promise的方法时,then处理程序都会总是异步执行-也就是说,之后它们下面的代码不在.then处理程序中。

这意味着当您返回data时,您定义的then处理程序尚未执行。这反过来意味着您返回的值没有及时设置为正确的值。

这里有一个关于这个问题的简单类比:

    function getFive(){var data;setTimeout(function(){ // Set a timer for one second in the futuredata = 5; // After a second, do this}, 1000);return data;}document.body.innerHTML = getFive(); // `undefined` here and not 5

data的值是undefined,因为data = 5部分还没有执行。它可能会在一秒钟内执行,但到那时它与返回值无关。

由于操作还没有发生(Ajax、服务器调用、I/O和计时器),您在请求有机会告诉您的代码该值是什么之前返回值。

这个问题的一个可能的解决方案是编码重新,告诉你的程序在计算完成后做什么。Promises通过本质上的时间(时间敏感)主动启用了这一点。

快速总结承诺

Promise是时间价值。Promise有状态。它们从没有值的挂起开始,可以稳定为:

  • 履行表示计算成功完成。
  • 拒绝表示计算失败。

Promise只能更改状态一次,之后它将永远保持相同的状态。您可以将then处理程序附加到Promise以提取它们的值并处理错误。then处理程序允许链接的调用。Promise由使用返回它们的API创建。例如,更现代的Ajax替换fetch或jQuery的$.get返回Promise。

当我们对一个Promise调用.then,并从中调用返回时,我们得到了的处理值的Promise。如果我们返回另一个Promise,我们会得到惊人的东西,但让我们保持冷静。

用承诺

让我们看看如何使用Promise解决上述问题。首先,让我们通过使用Promise构造函数创建延迟函数来演示我们对上述Promise状态的理解:

function delay(ms){ // Takes amount of milliseconds// Returns a new promisereturn new Promise(function(resolve, reject){setTimeout(function(){ // When the time is up,resolve(); // change the promise to the fulfilled state}, ms);});}

现在,在我们转换的设置超时使用Promise之后,我们可以使用then使其生效:

function delay(ms){ // Takes amount of milliseconds// Returns a new promisereturn 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 valuereturn 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, and we can't access the plain valuegetFive().then(function(five){document.body.innerHTML = five;});

基本上,不是返回,因为并发模型我们不能这样做-我们返回包装器的值,我们可以展开then。这就像一个可以用then打开的盒子。

应用此

这与您的原始API调用相同,您可以:

function foo() {// RETURN the promisereturn 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并链接它们来执行处理。我们现在知道如何从异步调用中返回响应。

ES2015(ES6)

ES6引入了发电机,这些函数可以在中间返回,然后恢复它们所在的点。这通常对序列有用,例如:

function* foo(){ // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js onlyyield 1;yield 2;while(true) yield 3;}

是一个在可以迭代的序列1,2,3,3,3,3,....上返回迭代器的函数。虽然这本身很有趣,并为很多可能性打开了空间,但有一个特别有趣的案例。

如果我们产生的序列是一个动作序列而不是数字序列——我们可以在产生动作时暂停函数,并在恢复函数之前等待它。因此,我们需要一个未来值序列而不是数字序列——即:承诺。

这是一个有点棘手但非常强大的技巧,让我们以同步的方式编写异步代码。有几个“跑步者”可以为你做到这一点。编写一个是短短的几行代码,但它超出了这个答案的范围。我在这里将使用Bluebird的Promise.coroutine,但也有其他包装器,如coQ.async

var foo = coroutine(function*(){var data = yield fetch("/echo/json"); // Notice the yield// The code here only executes _after_ the request is donereturn data.json(); // 'data' is defined});

此方法返回一个Promise本身,我们可以从其他协程中使用它。例如:

var main = coroutine(function*(){var bar = yield foo(); // Wait our earlier coroutine. It returns a promise// The server call is done here, and the code below executes when donevar baz = yield fetch("/api/users/" + bar.userid); // Depends on foo's resultconsole.log(baz); // Runs after both requests are done});main();

ES2016(ES7)

在ES7中,这被进一步标准化。现在有几个提议,但在所有这些提议中,你都可以await承诺。这只是上面ES6提议的“糖”(更好的语法),通过添加asyncawait关键字。制作上面的例子:

async function foo(){var data = await fetch("/echo/json"); // Notice the await// code here only executes _after_ the request is donereturn data.json(); // 'data' is defined}

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

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

下面是一个相同的例子:

var async = require("async");
// This wires up result back to the callervar 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 operationconsole.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 resultfoo(result);                       // Returns before the async completes

请注意,对foo()的调用仍然不会返回任何有用的内容。但是,异步调用的结果现在将存储在result.response中。

我们发现自己处在一个似乎沿着我们称之为“时间”的维度前进的宇宙中。我们并不真正理解时间是什么,但我们已经发展了抽象和词汇,让我们可以推理和谈论它:“过去”、“现在”、“未来”、“之前”、“之后”。

我们构建的计算机系统——越来越多地——将时间作为一个重要维度。某些事情被设置为在未来发生。然后,在最初的事情最终发生后,其他事情需要发生。这就是所谓的“异步”的基本概念。在我们日益网络化的世界中,异步最常见的情况是等待某个远程系统响应某个请求。

考虑一个例子。你打电话给送牛奶的人,点了一些牛奶。当牛奶来的时候,你想把它放在你的咖啡里。你现在不能把牛奶放在你的咖啡里,因为它还没有来。你必须等到它来的时候再把它放在你的咖啡里。换句话说,下面的方法是行不通的:

var milk = order_milk();put_in_coffee(milk);

因为JavaScript无法知道它需要等待才能让order_milk在执行put_in_coffee之前完成。换句话说,它不知道order_milk异步-直到未来某个时间才会产生牛奶。JavaScript和其他声明性语言执行一个又一个语句而无需等待。

解决这个问题的经典JavaScript方法,利用JavaScript支持函数作为可以传递的一流对象这一事实,是将一个函数作为参数传递给异步请求,然后在将来某个时候完成任务时调用该请求。这就是“回调”方法。它看起来像这样:

order_milk(put_in_coffee);

order_milk启动,订购牛奶,然后,当且仅当牛奶到达时,它调用put_in_coffee

这种回调方法的问题在于它污染了用return报告结果的函数的正常语义学;相反,函数不能通过调用作为参数给出的回调来报告结果。此外,当处理更长的事件序列时,这种方法很快就会变得笨重。例如,假设我想等待牛奶放入咖啡中,然后并且只有在那之后执行第三步,即喝咖啡。我最终需要写这样的内容:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

在这里,我将要放入的牛奶和要在牛奶放入后执行的操作(drink_coffee)传递给put_in_coffee。这样的代码变得难以编写、阅读和调试。

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

var answer;$.ajax('/foo.json') . done(function(response) {callback(response.data);});
function callback(data) {console.log(data);}

输入承诺

这就是“Promise”概念的动机,它是一种特殊类型的值,代表某种未来异步结果。它可以代表已经发生的事情,或者将来会发生的事情,或者可能永远不会发生。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应用于您的特定问题。我们将把我们的请求逻辑包装在一个函数中,该函数返回一个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); });

使用Promise时,我们最终会将许多函数传递给then,因此使用更紧凑的ES6风格的箭头函数通常会有所帮助:

get_data() .then(data => console.log(data));

async关键字

但是,对于必须以一种方式编写代码(如果是同步的)和完全不同的方式编写代码(如果是异步的),仍然存在一些模糊的不满。对于同步,我们写

a();b();

但是如果a是异步的,那么我们必须编写

a() . then(b);

上面,我们说,“JavaScript无法知道它需要等待才能在执行第二个调用之前完成第一个调用”。如果有的方法告诉JavaScript这一点,那不是很好吗?事实证明,有await关键字,在一种称为“async”函数的特殊类型中使用。此功能是即将推出的ECMAScript(ES)版本的一部分,但在给定正确预设的情况下,它已经在巴别塔等转译器中可用。这使我们能够简单地编写

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

虽然承诺和回调在许多情况下都能正常工作,但在后面表达以下内容是一种痛苦:

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.currentvar resultdoSomething(function(name) {result = namefiber.run()})Fiber.yield()return result}
Fiber(function() {var nameif (!name) {name = async1()}async2(name)// Make any number of async calls from here}

您可以查看项目这里

我写的下面的例子展示了如何

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

这个工作示例是自包含的。它将定义一个简单的请求对象,该对象使用windowsXMLHttpRequest对象进行调用。它将定义一个简单的函数来等待一堆Promise完成。

上下文。该示例正在查询Spotify Web API端点,以便为给定的查询字符串集搜索playlist对象:

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

对于每个项目,一个新的Promise将触发一个块-ExecutionBlock,解析结果,根据结果数组(即Spotifyuser对象列表)安排一组新的Promise,并在ExecutionProfileBlock异步执行新的HTTP调用。

然后,您可以看到一个嵌套的Promise结构,它允许您生成多个完全异步的嵌套HTTP调用,并通过Promise.all连接每个调用子集的结果。

注意最近的Spotifysearch API需要在请求标头中指定访问令牌:

-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/xmlhttprequestSimpleRequest = {call: function(what, response) {var request;if (window.XMLHttpRequest) { // Mozilla, Safari, ...request = new XMLHttpRequest();} else if (window.ActiveXObject) { // Internet Explorertry {request = new ActiveXObject('Msxml2.XMLHTTP');}catch (e) {try {request = new ActiveXObject('Microsoft.XMLHTTP');} catch (e) {}}}
// State changesrequest.onreadystatechange = function() {if (request.readyState === 4) { // Doneif (request.status === 200) { // Completeresponse(request.responseText)}elseresponse();}}request.open('GET', what, true);request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);request.send(null);}}
//PromiseAllvar 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 blockvar 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 resultsconsole.log("All response received " + results.length);console.log(JSON.stringify(results, null, 2));}
, function(error) { // Errorconsole.log(error);})
/////
},function(error) { // Errorconsole.log(error);});
<div id="console" />

I have extensively discussed this solution here.

答案很简单:你必须实现这样的回调:

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返回一个解析的承诺(它在返回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);});})();

我会用一个看起来很可怕的手绘漫画来回答。第二张图片是代码示例中resultundefined的原因。

在此处输入图像描述

以下是处理异步请求的一些方法:

  1. 浏览器承诺对象
  2. Q-JavaScript的Promise库
  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()成功中使用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();

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

// WRONGvar results = [];theArray.forEach(function(entry) {doSomethingAsync(entry, function(result) {results.push(result);});});console.log(results); // E.g., using them, returning them, etc.

示例:

// WRONGvar 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; }

The reason that doesn't work is that the callbacks from doSomethingAsync haven't run yet by the time you're trying to use the results.

So, if you have an array (or list of some kind) and want to do async operations for each entry, you have two options: Do the operations in parallel (overlapping), or in series (one after another in sequence).

Parallel

You can start all of them and keep track of how many callbacks you're expecting, and then use the results when you've gotten that many callbacks:

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:", JSON.stringify(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; }

(We could do away with expecting and just use results.length === theArray.length, but that leaves us open to the possibility that theArray is changed while the calls are outstanding...)

Notice how we use the index from forEach to save the result in results in the same position as the entry it relates to, even if the results arrive out of order (since async calls don't necessarily complete in the order in which they were started).

But what if you need to return those results from a function? As the other answers have pointed out, you can't; you have to have your function accept and call a callback (or return a Promise). Here's a callback version:

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:", JSON.stringify(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; }

Or here's a version returning a Promise instead:

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来拒绝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([1, 2, 3]).then(function(results) {console.log("Results:", JSON.stringify(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; }

(Or alternately, you could make a wrapper for doSomethingAsync that returns a promise, and then do the below...)

If doSomethingAsync gives you a Promise, you can use 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会忽略第二个和第三个参数,你可以直接把它传递给mapmap用三个参数调用它的回调,但大多数人大多数时候只使用第一个参数):

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:", JSON.stringify(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; }

Note that Promise.all resolves its promise with an array of the results of all of the promises you give it when they are all resolved, or rejects its promise when the first of the promises you give it rejects.

Series

Suppose you don't want the operations to be in parallel? If you want to run them one after another, you need to wait for each operation to complete before you start the next. Here's an example of a function that does that and calls a callback with the 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(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:", JSON.stringify(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; }

(Or, again, build a wrapper for doSomethingAsync that gives you a promise and do the below...)

If doSomethingAsync gives you a Promise, if you can use ES2017+ syntax (perhaps with a transpiler like Babel), you can use an async function with for-of and 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:", JSON.stringify(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; }

If you can't use ES2017+ syntax (yet), you can use a variation on the "Promise reduce" pattern (this is more complex than the usual Promise reduce because we're not passing the result from one into the next, but instead gathering up their results in an array):

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:", JSON.stringify(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; }

...which is less cumbersome with ES2015+ arrow functions:

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:", JSON.stringify(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框架中使用的双向数据绑定商店概念的地方之一,将非常适合您…

因此,如果您使用AngularReact或任何其他进行双向数据绑定或存储概念的框架,这个问题对您来说只是解决了,所以简单地说,您的结果在第一阶段是undefined,所以您在收到数据之前已经得到了result = undefined,然后一旦您得到结果,它将被更新并分配给您的Ajax调用的响应的新值……

但是,您如何在纯JavaScript或jQuery中做到这一点,例如您在这个问题中提出的问题?

你可以使用回调、承诺和最近observable来为你处理它。例如,在Promise中,我们有一些像success()then()这样的函数,它们将在你的数据准备好时执行。observable上的回调或订阅函数也是如此。

例如,在您使用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(); // The 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');});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

If the underlying function is not promisified

Step 1. Wrap the function with a callback into the nsynjs-aware wrapper (if it has a promisified version, you can skip this step):

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将逐步评估所有运算符和表达式,如果某些慢函数的结果未准备好,则暂停执行。

更多的例子是这里

2017年答案:您现在可以在每个当前浏览器和Node.js中完全做您想做的事情

这很简单:

  • 返回一个Promise
  • 使用等待,它会告诉JavaScript等待将Promise解析为一个值(如HTTP响应)
  • 异步关键字添加到父函数

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

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

wait在所有当前浏览器和Node.js8中均受支持

当然有很多方法,比如同步请求、承诺,但根据我的经验,我认为你应该使用回调方法。JavaScript的异步行为很自然。

因此,您的代码片段可以重写为稍微不同:

function foo() {var result;
$.ajax({url: '...',success: function(response) {myCallback(response);}});
return result;}
function myCallback(response) {// Does something.}

在看树之前,我们先看看森林。

这里有很多信息丰富的答案,我不会重复任何一个。用JavaScript编程的关键是首先拥有整体执行的正确的心智模型

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

好消息是,如果你很好地理解了这一点,你就永远不必担心竞争条件。你应该首先考虑如何组织代码,本质上是对不同离散事件的响应,以及如何将它们串联成一个逻辑序列。为此,你可以使用Promise或更高级别的新async/wait作为工具,也可以自己推出。

但是,在你对实际问题域感到满意之前,你不应该使用任何战术工具来解决问题。绘制这些依赖关系的地图以知道何时需要运行什么。尝试对所有这些回调采取临时方法不会对你有好处。

这是我们在与JavaScript的“奥秘”作斗争时面临的一个非常常见的问题。今天让我试着揭开这个谜团。

让我们从一个简单的JavaScript函数开始:

function foo(){// Do somethingreturn '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()立即返回的东西,它什么都不是,即只是undefined

那么,我们如何解决这个问题呢?

让我们向函数请求承诺。Promise实际上是关于它的含义:它意味着函数保证你提供它将来得到的任何输出。所以让我们看看上面的小问题的实际效果:

function foo(){return new Promise((resolve, reject) => { // I want foo() to PROMISE me somethingsetTimeout ( function(){// Promise is RESOLVED, when the execution reaches this line of coderesolve('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(您打算返回的值)。因此,简而言之,在异步函数中,您的值是解决而不是返回

UPDATE(异步/等待的承诺)

除了使用then/catch来处理Promise之外,还有一种方法。这个想法是识别异步函数,然后等待承诺来解决,然后再移动到下一行代码。它仍然只是底层的promises,但是采用了不同的语法方法。为了让事情更清楚,你可以在下面找到一个比较:

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

异步/等待版本:

  async function saveUsers(){try{let users = await getUsers()saveSomewhere(users);}catch(err){console.error(err);}}

问题是:

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

其中可以被解释为:

如何让异步代码看起来同步

解决方案将是避免回调,并使用Promises异步/等待的组合。

我想举一个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)
})

演示上面代码的Stack Snippet。

// The function declaration:async function foo() {var response = await $.ajax({url: 'https://jsonplaceholder.typicode.com/todos/1'})return response;}
// Execute it like this:(async function() {try {var result = await foo()console.log(result)} catch (e) {}})()
// Or use Promise syntax:foo().then(response => {console.log(response)}).catch(error => {console.log(error)})
.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>

JavaScript是单线程的。

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

  1. 事件循环

  2. Web API

  3. 事件队列

事件循环永远运行,即一种无限循环。事件队列是在某个事件上推送所有函数的地方(例如:单击)。

这是一个接一个地从队列中执行,并放入执行该函数的事件循环中,并在执行第一个函数后为下一个函数做好准备。这意味着一个函数的执行直到队列中的前一个函数在事件循环中执行时才开始。

现在让我们假设我们在队列中推送了两个函数。一个是从服务器获取数据,另一个利用该数据。我们首先在队列中推送serverRequest()函数,然后是utiliseData()函数。serverRequest函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据需要多少时间,所以这个过程预计需要时间,所以我们忙于事件循环从而挂起页面。

这就是Web API发挥作用的地方。它从事件循环中获取此函数并与服务器打交道,使事件循环免费,以便我们可以从队列中执行下一个函数。

队列中的下一个函数是utiliseData(),它会进入循环,但由于没有可用的数据,它会被浪费,下一个函数的执行会继续到队列的末尾。(这称为异步调用,即我们可以做其他事情,直到我们获得数据。)

让我们假设我们的serverRequest()函数在代码中有一个返回语句。当我们从服务器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");elseconsole.log("Data not found");}

JavaScript.info回调

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 doyield setTimeout(callback, 1000);console.log("it delayed for 1000ms");while (response.statusText === "error") {[response] = yield* anotherGenerator();}}

要运行上面的代码,您可以这样做:

const gen = myGenerator(); // Create generatorgen.next(); // Start itgen.next((...args) => gen.next([...args])); // Set its callback function

如果您需要针对不支持ES6的浏览器,您可以通过Babel或闭包编译器运行代码以生成ECMAScript 5。

回调...args包装在一个数组中,并在读取它们时进行非结构化,以便模式可以处理具有多个参数的回调。例如nodefs

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

与其把代码扔给你,还有两个概念是理解JavaScript如何处理回调和异步的关键(这是一个词吗?)

事件循环和并发模型

有三件事你需要注意;队列;事件循环和堆栈

广义地说,事件循环就像项目经理,它不断地侦听任何想要运行的函数,并在队列和堆栈之间进行通信。

while (queue.waitForMessage()) {queue.processNextMessage();}

一旦它收到要运行某些内容的消息,它就会将其添加到队列中。队列是等待执行的内容列表(例如您的AJAX请求)。想象一下:

  1. 使用fobarFunc调用foo.com/api/bar
  2. Go执行无限循环…等等

当其中一条消息要执行时,它会从队列中弹出消息并创建一个堆栈,堆栈是JavaScript执行消息中指令所需的一切。所以在我们的例子中,它被告知调用foobarFunc

function foobarFunc (var) {console.log(anotherFunction(var));}

因此,fobarFunc需要执行的任何内容(在我们的例子中anotherFunction)都将被推送到堆栈上。执行,然后被遗忘-事件循环将移动到队列中的下一件事(或监听消息)

这里的关键是执行的顺序。这是

什么时候有东西要跑

当您使用AJAX调用外部方或运行任何异步代码(例如setTimeout)时,JavaScript在继续之前依赖于响应。

最大的问题是它什么时候会得到响应?答案是我们不知道-所以事件循环正在等待该消息说“嘿,运行我”。如果JavaScript只是同步等待该消息,你的应用程序会冻结并且它会很糟糕。因此JavaScript继续执行队列中的下一项,同时等待消息被添加回队列。

这就是为什么在异步功能中我们使用回调的原因。-当传递给另一个函数时,将在以后执行的函数或处理程序。A承诺使用回调(例如传递给.then()的函数)作为一种更线性的方式来推理这种异步行为。承诺是一种说“I承诺在某个时候归还一些东西”的方式,回调是我们如何处理最终返回的值。jQuery使用称为deffered.donedeffered.faildeffered.always(以及其他)的特定回调。你可以看到它们全部这里

因此,您需要做的是传递一个函数,该函数承诺在某个时候使用传递给它的数据执行。

因为回调不是立即执行的,而是在稍后的时间将引用传递给不是它执行的函数是很重要的。所以

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

所以大多数时候(但不总是)你会通过foo而不是foo()

希望这会有一些意义。当你遇到这样的事情时,看起来很困惑-我强烈建议你完整地阅读留档,至少了解它。它会让你成为一个更好的开发人员。

这是一个有效的例子:

const validateName = async userName => {const url = "https://jsonplaceholder.typicode.com/todos/1";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))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>

将Node.js上的XHR转换为异步等待的简单代码示例

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;var xhttp = new XMLHttpRequest();
function xhrWrapWithPromise() {return new Promise((resolve, reject) => {xhttp.onreadystatechange = function() {if (this.readyState == 4) {if (this.status == 200) {resolve(this.responseText);} else {reject(new Error("Couldn't feth data finally"));}}};xhttp.open("GET", "https://www.w3schools.com/xml/xmlhttp_info.txt", true);xhttp.send();});}
// We need to wrap await in Async function so and anonymous IIFE here(async _ => {try {let result = await xhrWrapWithPromise();console.log(result);} catch (error) {console.log(error);}})();

使用Promise

这个问题最完美的答案是使用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有问题!

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

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

未捕获的引用错误:Promise未定义

所以我决定为ES3以下 JavaScript编译器实现我自己的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;}();}

async/await巴别塔这样的转译器一起使用,以使其在旧浏览器中正常工作。您还必须从npm:npm i -D babel-preset-env babel-polyfill安装此Babel预设和poly填充。

function getData(ajaxurl) {return $.ajax({url: ajaxurl,type: 'GET',});};
async test() {try {const res = await getData('https://api.icndb.com/jokes/random')console.log(res)} catch(err) {console.log(err);}}
test();

或者.then回调只是编写相同逻辑的另一种方式。

getData(ajaxurl).then(function(res) {console.log(res)}

等待

请求以异步方式工作,因此你不能像在典型代码中那样同步读取数据。但是,使用async/await你可以创建看起来接近/类似于通常的同步/顺序风格的异步代码。处理响应数据的代码需要由async函数包装(在下面的片段中为load),在其中你需要在foo()之前添加await关键字(也使用async/await)。

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

请记住,async函数总是(隐式)将其结果包装到一个Promise中(因此它返回一个Promise)。

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

  1. 在程序的流程中(想想代码甚至最低级别:机器代码),数据可能不会在2秒后返回,3秒后返回,或者可能根本不会到达,因此没有通常的return可用于返回数据。

  2. 这是经典的“观察者模式”。(它可以是“回调”的形式。)它是:“嘿,我有兴趣知道数据的成功到达;你能让我知道什么时候到达吗?”所以你注册一个观察者被通知(或调用一个函数来通知数据的成功到达。)你通常还会注册一个观察者来通知此类数据的失败到达。

  3. 当数据成功到达或返回此类数据失败时,注册的观察者(或回调)将与数据一起通知(或与数据一起调用)。如果观察者以回调函数foo的形式注册,那么foo(data)将被调用。如果观察者以对象foo的形式注册,那么根据接口,可能是foo.notify(data)被调用。

在阅读了这里的所有回复和我的经验之后,我想恢复JavaScript中异步编程callback, promise and async/await的细节。

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)承诺:语法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实例。解析函数表明Promise实例已完成。之后,我们可以在Promise实例上调用. time()(根据需要调用. time链)和. cat():

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)异步/等待:一个新的语法ES6-等待基本上是Promise的语法糖!

Async函数为我们提供了一个干净简洁的语法,使我们能够编写更少的代码来完成与Promise相同的结果。Async/A等待类似于同步代码,并且同步代码更容易读写。要使用Async/Aetc捕获错误,我们可以使用块try...catch。在这里,您不需要编写一连串的Promise语法。

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

结论:这完全是异步的三种语法你应该很好地理解的JavaScript编程。所以如果可能的话,我建议您使用“Promise”或“async/wait”进行重构您的异步代码(主要用于XHR请求)

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

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

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

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

选项#1-直接从foo方法返回Ajax调用。
在最新版本的jQuery中,Ajax调用返回一个Promise对象,可以使用.then函数解析。在代码中,.then函数前面是要解析的回调函数,在本例中为foo()

   // Declare function foofunction foo(url){return $.get(url);}
// Invoke the foo function, which returns a promise object// the 'then' function accepts the call back to the resolve functionfoo('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对象,在该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>

最初,回调用于异步操作(例如,在请求接口中)。现在,像浏览器获取API这样基于承诺的API已成为默认解决方案,所有现代浏览器和Node.js(服务器端)都支持更好的#0语法。

一个常见的场景-从服务器获取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-based的风格而不是回调。如果你继承了依赖回调的API,则可以将其包装为Promise:

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

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

由于await总是返回一个Promise,只需执行额外的await(在async函数中)即可提取值:

test(); // This alerts "hello"
// This is the outer function that wants to get the string result of inner()async function test() {var str=await inner();alert(str);} // test
// This ia an inner function that can do arbitrary async operationsfunction inner() {return Promise.resolve('hello');}

默认值async: false

我通过将async设置为false并重组我的Ajax调用来解决它:

我设置了一个名为sendRequest(type, url, data)的全局函数,每次都要调用三个参数:

function sendRequest(type, url, data) {let returnValue = null;$.ajax({url: url,type: type,async: false,data: data,dataType: 'json',success: function (resp) {returnValue = resp;}});return returnValue;}

现在调用函数:

let password = $("#password").val();let email = $("#email").val();let data = {email: email,password: password,};let  resp =  sendRequest('POST', 'http://localhost/signin')}}", data);console.log(resp);

代码中重要的说明是:async: false

如果此解决方案不适用于您,请注意,这可能无法在某些浏览器或jQuery版本中运行。

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);我以为会打印最后,实际上打印之前其他输出!-它不包含结果:它只打印undefined1为什么?

有用的见解

我清楚地记得我关于如何理解异步的第一个啊哈!时刻调用。
这一评论表示:
您实际上不想获取回调的数据
你想让你的数据需求操作回调!
2
这在上面的例子中很明显。
但是仍然有可能编写代码之后异步调用#36825;,一旦它完成了?

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

Note how the function asynchronousCall is void. It returns nothing.Instead, by calling asynchronousCall with an anonymous callback function(asynchronousCall(function (result) {...), this function executes thedesired actions on the result, but only after the request has completed –when the responseText is available.

Running the above snippet shows how I will probably not want to write any codeafter the asyncronous call (such as the lineLAST in the code, but executed FIRST!).
Why? – Because such code willhappen before the asyncronous call delivers any response data.
Doing so is bound to cause confusion when comparing the code with the output.

3. Promise with .then() – or async/await

The .then() construct was introduced in the ECMA-262 6th Edition in June2015, and the async/await construct was introduced in the ECMA-2628th Edition in June 2017.
The code below is still plain JavaScript, replacing the old-schoolXMLHttpRequest with Fetch.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; }

A word of warning is warranted if you decide to go with the async/awaitconstruct. Note in the above snippet how await is needed in two places.If forgotten in the first place, there will be no output. If forgotten in thesecond place, the only output will be the empty object, {}(or [object Object] or [object Promise]).
Forgetting the async prefix of the function is maybe the worst of all – theoutput will be "SyntaxError: missing ) in parenthetical" – no mentioning ofthe missing async keyword.

4. Promise.all – array of URLs 5

Suppose we need to request a whole bunch of URLs.I could send one request, wait till it responds, then send the next request,wait till it responds, and so on ...
Aargh! – That could take a loong time. Wouldn't it be better if I could sendthem all at once, and then wait no longer than it takes for the slowestresponse to arrive?

As a simplified example, I will use:

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结果数组(标题)是合并到:

namesonly = ['two', 'three']

所需的输出是将namesonlyurls组合成一个mashup对象数组

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

我将title的名称更改为loremipsum

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

All the above examples are short and succinctly convey how asynchronous callsmay be used on toyish APIs.Using small APIs works well to explain concepts and working code, but theexamples might be a bit of dry runs.

The next section will show a more realistic example on how APIs may becombined to create a more interesting output.

5. How to visualize a mashup in Postman 6

The MusicBrainz APIhas information about artists and music bands.
An example – a request for the British rock band Coldplay is:
http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&fmt=json&inc=url-rels+release-groups.
The JSON response contains – among other things – the 25 earliest album titlesby the band.This information is in the release-groups array.The start of this array, including its first object is:

..."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片段显示Coldplay的第一张专辑是降落伞。它还给出了一个id,在这种情况下1dc4c347-a1db-32aa-b14f-bc9cc507b843,这是相册的唯一标识符。

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

对于每个相册,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是降落伞专辑封面的直接链接。

创建和可视化mashup的代码

总体任务是使用Postman可视化所有专辑标题和正面乐队的封面。如何编写代码来实现这一点已经在相当多的地方进行了描述问题一个答案中的详细信息如何在Postman中可视化API混搭?我要避免这里进行了冗长的讨论,只提供代码和结果:

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="\{\{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,请执行以下操作:

  1. 下载并保存
    http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json
    在您的硬盘驱动器上的合适位置。

  2. 在Postman中,Ctrl+O>上传文件>MusicBands.pm_coll.json>导入
    您现在应该在Postman中的集合中看到MusicBands

  3. 集合>#0>#1>发送8

  4. 在邮递员响应正文中,单击可视化

  5. 您现在应该能够滚动15张专辑,如上面的截图。

参考文献


1由原海报表达为:他们都回来了undefined.
2如果您认为异步调用令人困惑,请考虑使用看看关于异步调用的一些问题和答案是否有帮助。
3名称XMLHttpRequestX中的名称一样具有误导性AJAX-现在Web API的数据格式普遍是JSON,而不是XML。
4获取返回Promise。我很惊讶地发现XMLHttpRequest相关文档拿过来都不是ECMAScript标准。JavaScript可以在这里访问它们的原因是因为Web浏览器提供了他们。获取标准XMLHttpRequest标准都支持Web超文本应用技术工作组(WHATWG)成立于2004年6月。
5本节借鉴了很多如何获取一个URL数组与Promise.all?.
6本节严重依赖于如何在Postman中可视化API混搭?.
7此URL会自动重定向到:https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json.
8如果您收到错误,运行你的脚本时出了问题,再试一次发送

使用asyncawait

示例代码:

const data = async() => {const res = await get('https://getdata.com')}

代码从我的工作它的工作,尝试在stf中搜索但未找到这样就可以了

  1. 声明ajax调用函数

         function get_LegalDataPOST_II(data, EndPoint) {var a_params = {sql_h: '',sql_txt: data}var data = JSON.stringify(a_params);if (EndPoint == undefined) {EndPoint = 'DB';}var sFormData = new FormData();sFormData.append("EndPoint", EndPoint);sFormData.append("Data", data);sFormData.append("ByUser", get_ul_Legal());
    return $.ajax({url: "home/xxxxPOST/",type: "POST",contentType: false,processData: false,data: sFormData,success: function (data) { },error: function () {}});}
  2. 获取数据

     var sql = " select * from foo ";get_LegalDataPOST_II(sql).done(function (data) {var data = JSON.parse(data);$(data).each(function () {
    });});

    方法发布如果想编码发布评论