How do I return the response from an asynchronous call?
我有一个函数
我尝试从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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`. |
→ For a more general explanation of async behaviour with different examples, please see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
Ok.
→ If you already understand the problem, skip to the possible solutions below.
Ok.
问题
ajax中的a代表异步。这意味着发送请求(或者更确切地说,接收响应)会脱离正常的执行流。在您的示例中,
下面是一个类比,希望能使同步流和异步流之间的区别更清楚:好的。同步的
想象一下,你打电话给一个朋友,让他帮你找点东西。虽然这可能需要一段时间,但你还是要等在电话里,凝视着太空,直到你的朋友给你所需要的答案。好的。
当您进行包含"normal"代码的函数调用时,也会发生同样的情况:好的。
1 2 3 4 5 6 7 8 9 10 11 12 | function findItem() { var item; while(item_not_found) { // search } return item; } var item = findItem(); // Do something with item doSomethingElse(); |
即使执行
你再次打电话给你的朋友也是出于同样的原因。但这次你告诉他你很忙,他应该用你的手机给你回电话。你挂断电话,离开房子,做你计划做的任何事。一旦你的朋友给你回电话,你就要处理他给你的信息。好的。
这正是执行Ajax请求时发生的事情。好的。
1 2 3 4 | findItem(function(item) { // Do something with item }); doSomethingElse(); |
不是等待响应,而是立即继续执行,并在执行Ajax调用之后执行语句。为了最终得到响应,您提供了一个函数,在收到响应后调用该函数,一个回调(注意到什么吗?回电话?)。在调用回调之前执行该调用之后的任何语句。好的。解(S)
拥抱JavaScript的异步特性!虽然某些异步操作提供了同步的对应项(Ajax也是如此),但通常不鼓励使用它们,特别是在浏览器上下文中。好的。
你问为什么不好?好的。
javascript在浏览器的ui线程中运行,任何长时间运行的进程都会锁定该ui,使其无响应。另外,javascript的执行时间有一个上限,浏览器会询问用户是否继续执行。好的。
所有这些都是非常糟糕的用户体验。用户无法判断是否一切正常。此外,对于连接速度较慢的用户来说,效果会更差。好的。
在下面我们将看到三种不同的解决方案,它们都是在彼此之上构建的:好的。
- 与
async/await 的承诺(ES2017+,如果您使用蒸腾器或再生器,在旧浏览器中提供) - 回调(在节点中流行)
- 与
then() 的承诺(ES2015+,如果您使用许多承诺库中的一个,则在旧浏览器中提供)
这三种浏览器都可以在当前浏览器和节点7+中使用。好的。ES2017+:与
2017年发布的ECMAScript版本引入了对异步函数的语法级支持。在
重要提示:您只能在
您可以在MDN上阅读更多关于
下面是一个建立在上述延迟之上的示例:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // 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); })(); |
当前浏览器和节点版本支持
回调只是传递给另一个函数的函数。另一个函数可以随时调用传递的函数。在异步进程的上下文中,每当异步进程完成时都将调用回调。通常,结果会传递到回调。好的。
在这个问题的例子中,您可以使
1 2 | var result = foo(); // Code that depends on 'result' |
变成好的。
1 2 3 | foo(function(result) { // Code that depends on 'result' }); |
这里我们定义了函数"inline",但是您可以传递任何函数引用:好的。
1 2 3 4 5 | function myCallback(result) { // Code that depends on 'result' } foo(myCallback); |
1 2 3 4 5 6 | function foo(callback) { $.ajax({ // ... success: callback }); } |
您还可以在将响应传递给回调之前对其进行处理:好的。
1 2 3 4 5 6 7 8 9 | function foo(callback) { $.ajax({ // ... success: function(response) { // For example, filter the response callback(filtered_response); } }); } |
使用回调编写代码比看起来更容易。毕竟,浏览器中的javascript是严重的事件驱动(dom事件)。接收Ajax响应只是一个事件。当您必须使用第三方代码时,可能会出现一些困难,但大多数问题都可以通过应用程序流进行思考来解决。好的。ES2015+:与then()的承诺
Promise API是EcmaScript 6(ES2015)的一个新功能,但它已经具有良好的浏览器支持。还有许多库实现了标准的PromisesAPI,并提供了额外的方法来简化异步函数(如Bluebird)的使用和组合。好的。
承诺是未来价值的容器。当承诺收到该值(已解决)或取消(拒绝)时,它会通知所有想要访问该值的"侦听器"。好的。
与普通回调相比的优势在于,它们允许您分离代码,并且更容易组合。好的。
以下是使用承诺的简单示例:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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调用,我们可以使用如下承诺:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function ajax(url) { return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.onload = function() { resolve(this.responseText); }; xhr.onerror = reject; xhr.open('GET', url); xhr.send(); }); } ajax("/echo/json") .then(function(result) { // Code depending on result }) .catch(function() { // An error occurred }); |
描述Promise提供的所有优势都超出了这个答案的范围,但是如果编写新代码,您应该认真考虑它们。它们提供了代码的一个伟大的抽象和分离。好的。
关于承诺的更多信息:html5 rocks-javascript承诺好的。旁注:jquery的延迟对象
延迟对象是jquery的承诺自定义实现(在Promise API标准化之前)。它们的行为类似于承诺,但公开了略有不同的API。好的。
jquery的每个ajax方法都已经返回了一个"延迟对象"(实际上是一个延迟对象的承诺),您可以从函数返回它:好的。
1 2 3 4 5 6 7 8 9 | function ajax() { return $.ajax(...); } ajax().done(function(result) { // Code depending on result }).fail(function() { // An error occurred }); |
旁注:承诺
记住承诺和延迟对象只是未来价值的容器,而不是价值本身。例如,假设您有以下条件:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 } |
此代码误解了上述异步问题。具体来说,
但解决方法很简单:好的。
1 2 3 4 5 6 7 8 9 10 11 | 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
如果直接使用
如果使用jquery,可以将
1 2 3 4 5 6 7 | function foo() { var jqXHR = $.ajax({ //... async: false }); return jqXHR.responseText; } |
如果使用其他jquery ajax方法,如
抬起头来!不可能发出同步JSONP请求。JSONP本质上总是异步的(还有一个原因是甚至不考虑这个选项)。好的。好啊。
如果您的代码中没有使用jquery,那么这个答案是为您准备的。
您的代码应该是这样的:
1 2 3 4 5 6 7 8 | function foo() { var httpRequest = new XMLHttpRequest(); httpRequest.open('GET',"/echo/json"); httpRequest.send(); return httpRequest.responseText; } var result = foo(); // always ends up being 'undefined' |
费利克斯·克林为使用jquery for ajax的人写了一个很好的答案,我决定为那些不使用jquery的人提供一个替代方案。
(注意,对于那些使用新的
这是另一个答案中"问题解释"的简短摘要,如果阅读后您不确定,请阅读。
ajax中的a代表异步。这意味着发送请求(或者更确切地说,接收响应)会脱离正常的执行流。在您的示例中,
这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。
下面是一个简单的类比
1 2 3 4 5 6 7 | function getFive(){ var a; setTimeout(function(){ a=5; },10); return a; } |
(小提琴)
由于
解决这个问题的一个可能的方法是重新编写代码,告诉程序计算完成后该做什么。
1 2 3 4 5 6 7 8 9 10 11 | function onComplete(a){ // When the code completes, do this alert(a); } function getFive(whenDone){ var a; setTimeout(function(){ a=5; whenDone(a); },10); } |
这叫做CPS。基本上,我们传递
用途是:
1 | getFive(onComplete); |
它应该在屏幕上提示"5"。(小提琴)
可能的解决方案解决这个问题的基本方法有两种:
1。同步Ajax-不要这样做!!
至于同步Ajax,不要这样做!费利克斯的回答提出了一些令人信服的论据,解释为什么这是一个坏主意。总而言之,它将冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。下面是另一个简短的总结,摘自MDN,内容是:
XMLHttpRequest supports both synchronous and asynchronous communications. In general, however, asynchronous requests should be preferred to synchronous requests for performance reasons.
In short, synchronous requests block the execution of code... ...this can cause serious issues...
如果必须这样做,您可以传递一个标志:以下是方法:
1 2 3 4 5 6 7 | 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。重组代码
让函数接受回调。在示例代码中,可以使
所以:
1 2 | var result = foo(); // code that depends on `result` goes here |
变成:
1 2 3 | foo(function(result) { // code that depends on `result` }); |
在这里,我们传递了一个匿名函数,但是我们可以很容易地传递对现有函数的引用,使其看起来像:
1 2 3 4 | function myHandler(result) { // code that depends on `result` } foo(myHandler); |
有关如何完成这种回调设计的更多详细信息,请查看Felix的答案。
现在,让我们定义foo本身以便采取相应的行动
1 2 3 4 5 6 7 8 | 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入门指南。
XMLHttpRequest2(首先阅读Benjamin Gruenbaum和Felix Kling的答案)好的。
如果您不使用jquery,并且想要一个适用于现代浏览器和移动浏览器的漂亮的短xmlhttprequest 2,我建议您这样使用:好的。
1 2 3 4 5 6 | function ajax(a, b, c){ // URL, callback, just a placeholder c = new XMLHttpRequest; c.open('GET', a); c.onload = b; c.send() } |
正如你所看到的:好的。
有两种方法可以获得此Ajax调用的响应(三种方法使用xmlhttpRequest变量名):好的。
最简单的:好的。
1 | this.response |
或者,如果出于某种原因,您
1 | e.target.response |
例子:好的。
1 2 3 4 | function callback(e){ console.log(this.response); } ajax('URL', callback); |
或者(上面的一个更好,匿名函数总是一个问题):好的。
1 | ajax('URL', function(e){console.log(this.response)}); |
没有比这更简单的了。好的。
现在有些人可能会说,最好使用onreadystatechange甚至xmlhttprequest变量名。这是错误的。好的。
检查xmlhttpRequest高级功能好的。
它支持所有*现代浏览器。我可以确认,因为存在xmlhttprequest2,所以我正在使用这种方法。在我使用的所有浏览器上,我从来没有遇到过任何类型的问题。好的。
OnReadyStateChange仅在希望获取状态2上的头时才有用。好的。
使用
现在,如果您希望使用post和formdata实现更复杂的功能,可以轻松地扩展此函数:好的。
1 2 3 4 5 6 | 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) } |
再一次。。。这是一个非常短的函数,但它确实得到&post。好的。
用法示例:好的。
1 2 | 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 |
或者传递一个完整的元素(
1 2 | var fd = new FormData(form); x(url, callback, 'post', fd); |
或者设置一些自定义值:好的。
1 2 3 | var fd = new FormData(); fd.append('key', 'val') x(url, callback, 'post', fd); |
如你所见,我没有实现同步…这是件坏事。好的。
这么说……为什么不这么简单?好的。
正如评论中提到的,使用错误和同步确实完全打破了答案的要点。哪种方法是以正确的方式使用Ajax的好方法?好的。
错误处理程序好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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数组缓冲区或其他任何类型,错误处理程序可能很有用…好的。
即使将"postapapp"作为方法传递,它也不会抛出错误。好的。
即使将"fdgdgilfdghfldj"作为formdata传递,也不会引发错误。好的。
在第一种情况下,错误在
在第二种情况下,它只是工作。如果您通过了正确的日志数据,则必须在服务器端进行检查。好的。
不允许跨域自动引发错误。好的。
在错误响应中,没有错误代码。好的。
只有
如果完全无法控制错误,为什么还要添加错误处理程序?大多数错误都是在回调函数
所以:如果您能够正确地复制和粘贴URL,就不需要进行错误检查。;)好的。
PS:作为第一个测试,我写了x("x",displayAjax)…,它完全得到了响应……????所以我检查了HTML所在的文件夹,其中有一个名为"x.xml"的文件。所以即使您忘记了文件的扩展名xmlhttprequest 2也会找到它。我爱你好的。
同步读取文件好的。
不要那样做。好的。
如果您想暂时阻止浏览器,请同步加载一个大的
1 2 3 4 5 6 | function omg(a, c){ // URL c = new XMLHttpRequest; c.open('GET', a, true); c.send(); return c; // Or c.response } |
现在你可以做好的。
1 | var res = omg('thisIsGonnaBlockThePage.txt'); |
没有其他方法可以以非异步方式完成这项工作。(是的,设置超时循环…但是严重吗?)好的。
另一点是…如果您使用API或您自己列表中的文件,或者您总是为每个请求使用不同的函数…好的。
只有当你有一个页面,你总是加载相同的XML/JSON或者你只需要一个函数的任何东西时。在这种情况下,稍微修改ajax函数,并用您的特殊函数替换b。好的。
以上功能仅供基本使用。好的。
如果要扩展函数…好的。
是的,你可以。好的。
我使用了大量的API,我集成到每个HTML页面中的第一个函数就是这个答案中的第一个Ajax函数,其中只有get-only…好的。
但是你可以用xmlhttprequest 2做很多事情:好的。
我做了一个下载管理器(在简历、文件阅读器、文件系统的两边都使用范围),各种图像大小调整器转换程序使用画布,用base64图像填充Web SQL数据库等等…但在这些情况下,您应该只为这个目的创建一个函数…有时候你需要一个blob,数组缓冲区,你可以设置头文件,覆盖mimetype,还有更多的…好的。
但这里的问题是如何返回Ajax响应…(我添加了一个简单的方法。)好的。好啊。
如果你使用承诺,这个答案是为你。
这意味着AngularJS、jQuery(带有延迟)、原生XHR替换(fetch)、EmberJS、BackboneJS的save或任何返回承诺的节点库。好的。
您的代码应该是这样的:好的。
1 2 3 4 5 6 7 8 9 10 | 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. |
FelixKling为使用jQuery和Ajax回调的用户编写了一个很好的答案。我有一个本地XHR的答案。这个答案适用于前端或后端承诺的一般用法。好的。核心问题
浏览器和服务器上使用nodejs/io.js的javascript并发模型是异步的和反应式的。好的。
每当调用返回Promise的方法时,
这意味着当您返回
以下是对这个问题的简单类比:
1 2 3 4 5 6 7 8 | 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 |
好的。
由于
由于操作尚未发生(Ajax、服务器调用、IO、计时器),所以在请求有机会告诉代码该值是什么之前,您将返回该值。好的。
解决这个问题的一个可能的方法是重新编写代码,告诉程序计算完成后该做什么。承诺在本质上是暂时的(时间敏感的),从而积极地实现这一点。好的。快速回顾承诺
承诺是一种价值。承诺是有状态的,它们开始时是无价值的,可以解决:好的。
- 完成意味着计算成功完成。
- 拒绝意味着计算失败。
一个承诺只能改变一次状态,此后它将永远保持在同一状态。您可以将
当我们对一个承诺打电话给
让我们看看如何用承诺来解决上述问题。首先,让我们通过使用Promise构造函数创建延迟函数来演示我们从上面对Promise状态的理解:好的。
1 2 3 4 5 6 7 8 | 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转换为使用承诺之后,我们可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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; }); |
好的。
基本上,我们不是返回一个由于并发模型而无法返回的值,而是返回一个包装器,用于一个可以用
这与最初的API调用相同,您可以:好的。
1 2 3 4 5 6 7 8 9 10 | 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` }) |
所以这也同样有效。我们已经了解到,我们不能从已经异步的调用中返回值,但是我们可以使用承诺并将它们链接起来以执行处理。现在我们知道了如何从异步调用返回响应。好的。ES2015(ES6)
ES6引入了生成器,这些函数可以在中间返回,然后恢复到原来的位置。这通常对序列有用,例如:好的。
1 2 3 4 5 | 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 4 5 | 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 }); |
这个方法返回一个承诺本身,我们可以从其他协程中使用它。例如:好的。
1 2 3 4 5 6 7 | 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中,这是进一步标准化的,目前有几个建议,但在所有这些建议中,您都可以保证。通过添加
1 2 3 4 5 | 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不正确。其想法是不让它返回任何东西,而是将数据交给一个称为回调函数的处理数据的函数。
即:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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函数中使用它来做您想要的事情。
最简单的解决方案是创建一个javascript函数并为Ajax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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); }); |
我想回答一个简单的发现,手工绘制的漫画。第二个原因是为什么
角1
对于使用AngularJS的人,可以使用
这里说,
Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together.
你也可以在这里找到一个很好的解释。
下面提到的文档中的示例。
1 2 3 4 5 6 7 8 9 10 11 | 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及更高版本
在
1 2 3 4 5 | search(term: string) { return this.http .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`) .map((response) => response.json()) .toPromise(); |
}
你可以这样消费,
1 2 3 4 5 6 7 | search() { this.searchService.search(this.searchField.value) .then((result) => { this.result = result.artists.items; }) .catch((error) => console.error(error)); } |
请看这里的原始帖子。但typescript不支持本机ES6承诺,如果您想使用它,可能需要插件。
此外,这里是这里定义的承诺规范。
这里的大多数答案都为您何时执行单个异步操作提供了有用的建议,但有时,当您需要对数组或其他类似列表的结构中的每个条目执行异步操作时,就会出现这种情况。诱惑是这样做:
1 2 3 4 5 6 7 8 | // WRONG var results = []; theArray.forEach(function(entry) { doSomethingAsync(entry, function(result) { results.push(result); }); }); console.log(results); // E.g., using them, returning them, etc. |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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)); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
不起作用的原因是,当您试图使用结果时,来自
因此,如果您有一个数组(或某种列表),并且想要对每个条目执行异步操作,那么您有两个选项:并行(重叠)或串联(依次)执行操作。
平行您可以启动所有回调并跟踪预期的回调次数,然后在收到如此多的回调后使用结果:
1 2 3 4 5 6 7 8 9 10 11 | 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 } }); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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)); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
(我们可以不使用
请注意,我们如何使用
但是如果您需要从函数返回这些结果呢?正如其他答案所指出的,您不能这样做;您必须让函数接受并调用回调(或者返回一个承诺)。以下是回调版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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)); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
或者这里有一个返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); }); |
当然,如果
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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)); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
(或者,您可以为返回承诺的
如果
1 2 3 4 5 6 7 8 9 10 | function doSomethingWith(theArray) { return Promise.all(theArray.map(function(entry) { return doSomethingAsync(entry, function(result) { results.push(result); }); })); } doSomethingWith(theArray).then(function(results) { console.log("Results:", results); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function doSomethingWith(theArray) { return Promise.all(theArray.map(function(entry) { return doSomethingAsync(entry, function(result) { results.push(result); }); })); } 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)); }); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
请注意,
假设您不希望操作并行?如果您想一个接一个地运行它们,您需要等待每个操作完成,然后再开始下一个操作。下面是一个函数的示例,该函数执行此操作并使用结果调用回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); }); |
(因为我们是一系列的工作,所以我们可以使用
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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)); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
(或者,再次为EDOCX1[0]构建一个包装,它给您一个承诺,并执行下面的操作…)
如果
1 2 3 4 5 6 7 8 9 10 | 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); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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)); }); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
如果您还不能使用ES2017+语法,您可以使用"Promise Reduce"模式的变体(这比通常的Promise Reduce更复杂,因为我们不会将结果从一个传递到下一个,而是将结果聚集到一个数组中):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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)); }); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
…ES2015+arrow功能不那么麻烦:
1 2 3 4 5 6 7 8 9 | 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); }); |
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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)); }); } |
1 2 3 | .as-console-wrapper { max-height: 100% !important; } |
有一个例子:看看这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); }); }); |
你可以看到
这是plnkr:
embed.plnkr.co http:/ / / / xlnr7hpcaihjxskmjfsg
es6(=单等待着)
1 2 3 4 5 6 7 8 9 10 11 | (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); }); })(); |
从异步函数返回值的另一种方法是传入一个对象,该对象将存储来自异步函数的结果。
下面是一个相同的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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'); }); |
我正在使用
我经常使用这种方法。我有兴趣知道这种方法在涉及到通过连续模块将结果连接回的地方工作得有多好。
这是许多新的javascript框架中使用的两种数据绑定方式之一,它们将对您非常有用…
因此,如果您使用的是角度、反应或任何其他的框架,它们可以进行两种方式的数据绑定,那么这个问题对您来说是简单的,所以简单来说,您的结果在第一阶段是
但您如何在纯javascript或jquery中实现它,例如您在这个问题中所问的那样?
您可以使用回调、Promise和Recently Observable来处理它,例如在Promise中,我们有一些函数,如success()或then(),当您的数据准备就绪时将执行这些函数,与Observable上的回调或订阅函数相同。
例如,在您使用jquery的情况下,您可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $(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 }); |
要了解更多关于承诺和可观察性的信息,它们是实现异步的新方法。
虽然承诺和回拨在许多情况下都很有效,但表达以下内容却是一种痛苦:
1 2 3 4 | if (!name) { name = async1(); } async2(name); |
你最终会通过
1 2 3 4 5 6 7 8 9 | async1(name, callback) { if (name) callback(name) else { doSomething(callback) } } async1(name, async2) |
虽然在一些小例子中它是可以的,但是当您有许多类似的情况和涉及错误处理时它会变得烦人。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 } |
你可以在这里签出这个项目。
简而言之,您必须实现如下回调:
1 2 3 4 5 6 7 8 9 | function callback(response) { // Here you can do what ever you want with the response object. console.log(response); } $.ajax({ url:"...", success: callback }); |
我编写的以下示例演示了如何
- 处理异步HTTP调用;
- 等待每个API调用的响应;
- 使用承诺模式;
- 使用promise.all模式连接多个HTTP调用;
这个工作示例是独立的。它将定义一个简单的请求对象,该对象使用window
语境。该示例查询spotify web api端点,以便搜索给定查询字符串集的
1 2 3 4 | [ "search?type=playlist&q=%22doom%20metal%22", "search?type=playlist&q=Adele" ] |
对于每个项目,新的Promise将触发一个块-
然后,您可以看到一个嵌套的Promise结构,它允许您生成多个完全异步的嵌套HTTP调用,并通过
注释最近的spotify
1 | -H"Authorization: Bearer {your access token}" |
因此,要运行以下示例,需要将访问令牌放在请求头中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 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); }); |
1 |
我在这里广泛地讨论了这个解决方案。
2017年答案:您现在可以在当前的每个浏览器和节点中完全按照自己的意愿进行操作。
这很简单:
- 兑现诺言
- 使用"wait",它将告诉javascript等待将要解析为值的承诺(如HTTP响应)。
- 向父函数添加'async'关键字
以下是您的代码的有效版本:
1 2 3 4 5 6 | (async function(){ var response = await superagent.get('...') console.log(response) })() |
所有当前浏览器和节点8都支持等待
您可以使用此自定义库(使用Promise编写)进行远程调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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); }; }); } |
简单用法示例:
1 2 3 4 5 6 7 8 | $http({ method: 'get', url: 'google.com' }).then(function(response) { console.log(response); }, function(error) { console.log(error) }); |
Js is a single threaded.
浏览器可分为三部分:
1)事件循环
2)Web API
3)事件队列
事件循环将永远运行,也就是无限循环。事件队列是将所有函数推送到某个事件上的地方(例如:单击)。这是一个接一个地从队列中执行并放入事件循环中,该事件循环执行此函数,并在执行第一个函数后为下一个函数做好准备。这意味着直到f在事件循环中执行它之前的函数。
现在让我们假设在一个队列中推送了两个函数,一个用于从服务器获取数据,另一个用于使用该数据。我们先推送队列中的serverRequest()函数,然后使用data()函数。serverRequest函数进入事件循环并调用服务器,因为我们不知道从服务器获取数据需要多长时间。因此,这个过程需要花费时间,因此我们忙于事件循环,从而挂起页面,这就是Web API的作用所在,它从事件循环中获取这个函数,并处理使事件循环免费的服务器,以便我们可以从队列中执行下一个函数。队列中的下一个函数是utilData(),它进入循环,但由于没有数据Av可能会浪费时间,下一个函数的执行会一直持续到队列的末尾。(这称为异步调用,也就是说,在获取数据之前,我们可以做其他事情)
假设我们的serverRequest()函数在代码中有一个RETURN语句,当我们从服务器Web API返回数据时,它会将数据推送到队列末尾的队列中。当它在队列的末尾被推送时,我们不能利用它的数据,因为队列中没有函数来利用这些数据,因此不可能从异步调用中返回某些内容。
因此,解决这个问题的方法是回调或承诺。
这里的一个答案的图片,正确解释了回调的用法…我们将函数(使用从服务器返回的数据的函数)赋给函数调用服务器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); } |
在我的代码中,它被称为
1 2 3 4 5 6 7 8 | 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 answer)https://stackoverflow.com/a/1422033/7579856
另一种解决方案是通过顺序执行器nsynjs执行代码。
如果基础函数是预先确定的NSYNJS将依次评估所有承诺,并将承诺结果放入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function synchronousCode() { var getURL = function(url) { return window.fetch(url).data.text().data; }; var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'; console.log('received bytes:',getURL(url).length); }; nsynjs.run(synchronousCode,{},function(){ console.log('synchronousCode done'); }); |
1 | <script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"> |
步骤1。将带有回调的函数包装到NSynJS感知包装中(如果它具有预先确定的版本,则可以跳过此步骤):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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。将同步逻辑投入功能:
1 2 3 | function process() { console.log('got data:', ajaxGet(nsynjsCtx,"data/file1.json").data); } |
步骤3。通过nsynjs同步运行函数:
1 2 3 | nsynjs.run(process,this,function () { console.log("synchronous function finished"); }); |
nsynjs将逐步评估所有的操作符和表达式,如果某个慢函数的结果没有准备好,则暂停执行。
这里有更多的例子:https://github.com/amaksr/nsynjs/tree/master/examples
这是我们在与JavaScript的"神秘"斗争时面临的一个非常常见的问题。今天让我试着揭开这个谜的神秘面纱。
让我们从一个简单的javascript函数开始:
1 2 3 4 5 6 | function foo(){ // do something return 'wohoo'; } let bar = foo(); // bar is 'wohoo' here |
这是一个简单的同步函数调用(其中每一行代码都在下一行代码之前"完成了它的工作"),结果与预期相同。
现在,让我们通过在函数中引入一点延迟来增加一些扭曲,这样所有代码行就不会按顺序"完成"。因此,它将模拟函数的异步行为:
1 2 3 4 5 6 7 | function foo(){ setTimeout( ()=>{ return 'wohoo'; }, 1000 ) } let bar = foo() // bar is undefined here |
你看,这个延迟刚刚破坏了我们期望的功能!但到底发生了什么?好吧,如果你看代码的话,这是非常合乎逻辑的。函数
那么,我们如何解决这个问题呢?
让我们向我们的职能部门请求一个承诺。Promise实际上是关于它的含义:它意味着该函数保证您提供将来得到的任何输出。因此,让我们看看上面这个小问题的实际情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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的调用等,您可以使用对
除了使用
1 2 3 4 5 6 7 8 9 | function fetchUsers(){ let users = []; getUsers() .then(_users => users = _users) .catch(err =>{ throw err }) return users; } |
异步/等待版本:
1 2 3 4 5 6 7 8 9 | async function fetchUsers(){ try{ let users = await getUsers() return users; } catch(err){ throw err; } } |
这里是一些方法与异步请求:
jQuery的例子:推迟执行与多个请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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(); |
ECMAScript 6具有"生成器",允许您以异步方式轻松编程。
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); } } |
要运行上述代码,请执行以下操作:
1 2 3 | const gen = myGenerator(); // Create generator gen.next(); // Start it gen.next((...args) => gen.next([...args])); // Set its callback function |
如果需要针对不支持ES6的浏览器,可以通过babel或闭包编译器运行代码来生成ecmascript 5。
回调
1 | const [err, data] = yield fs.readFile(filePath,"utf-8", callback); |
在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); |
简短回答:您的
在这个线程中给出了几个解决方案。也许最简单的方法是将一个对象传递给
1 2 3 4 5 6 7 8 9 10 11 | 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 |
注意,对
我们发现自己在一个宇宙中,这个宇宙似乎沿着我们称之为"时间"的维度发展。我们并不真正理解时间是什么,但我们已经开发出抽象和词汇,让我们思考和谈论它:"过去"、"现在"、"未来"、"之前"、"之后"。好的。
我们建立的计算机系统——越来越多——有时间作为一个重要的维度。某些事情将在未来发生。然后其他事情需要在第一件事情最终发生之后发生。这就是所谓的"异步"的基本概念。在我们日益网络化的世界中,最常见的异步情况是等待一些远程系统响应某些请求。好的。
考虑一个例子。你给送牛奶的人打电话要些牛奶。当它来的时候,你想把它放在你的咖啡里。你现在不能把牛奶放进咖啡里,因为它还没到。你得等它来了再把它放进咖啡里。换句话说,以下内容不起作用:好的。
1 2 | var milk = order_milk(); put_in_coffee(milk); |
因为JS无法知道它需要等待
对于这个问题,经典的JS方法利用了这样一个事实,即JS支持作为可以传递的第一类对象的函数,将函数作为参数传递给异步请求,然后在将来某个时候完成任务时调用该请求。这就是"回调"方法。看起来是这样的:好的。
1 | order_milk(put_in_coffee); |
这种回调方法的问题在于,它污染了用
1 | order_milk(function(milk) { put_in_coffee(milk, drink_coffee); } |
我要把牛奶和牛奶放进去的行动(
在这种情况下,我们可以将问题中的代码重写为:好的。
1 2 3 4 5 6 7 8 | var answer; $.ajax('/foo.json') . done(function(response) { callback(response.data); }); function callback(data) { console.log(data); } |
输入承诺
这就是"承诺"概念的动机,承诺是一种特殊的价值类型,代表某种未来或异步结果。它可以代表已经发生的事情,或者将来将要发生的事情,或者根本不会发生的事情。承诺有一个单一的方法,名为
在牛奶和咖啡的情况下,我们设计
1 | order_milk() . then(put_in_coffee) |
这样做的一个好处是,我们可以将它们串在一起,以创建未来出现的序列("链接"):好的。
1 | order_milk() . then(put_in_coffee) . then(drink_coffee) |
让我们将承诺应用于您的特定问题。我们将把请求逻辑包装在一个函数中,该函数返回一个承诺:好的。
1 2 3 | function get_data() { return $.ajax('/foo.json'); } |
实际上,我们所做的就是在对EDOCX1的调用中添加一个
1 | get_data() . then(do_something) |
例如,好的。
1 2 | get_data() . then(function(data) { console.log(data); }); |
当使用promises时,我们最终会将许多函数传递到
1 2 | get_data() . then(data => console.log(data)); |
但是对于必须以一种方式编写代码(如果是同步的话)和以一种完全不同的方式编写代码(如果是异步的话),仍然有一些模糊的不满。对于同步,我们写好的。
1 2 | a(); b(); |
但是如果
1 | a() . then(b); |
上面,我们说,"JS没有办法知道它需要等待第一个调用完成,然后再执行第二个"。如果有什么方法可以告诉JS那不是很好吗?原来有--EDOCX1[11]关键字,在一个称为"async"函数的特殊类型的函数中使用。这项功能是即将推出的ES版本的一部分,但在Babel等蒸腾器中已经提供了正确的预设。这让我们可以简单地写好的。
1 2 3 4 5 | async function morning_routine() { var milk = await order_milk(); var coffee = await put_in_coffee(milk); await drink(coffee); } |
在你的情况下,你可以写一些好的。
1 2 3 4 | async function foo() { data = await get_data(); console.log(data); } |
好啊。
当然,有很多方法,比如同步请求、承诺,但是根据我的经验,我认为您应该使用回调方法。JavaScript的异步行为是很自然的。因此,您的代码片段可以重写得稍有不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function foo() { var result; $.ajax({ url: '...', success: function(response) { myCallback(response); } }); return result; } function myCallback(response) { // Does something. } |
问题是:
How do I return the response from an asynchronous call?
可以解释为:
How to make asynchronous code look synchronous?
解决方案是避免回调,并使用承诺和异步/等待的组合。
我想给出一个Ajax请求的例子。
(虽然它可以用javascript编写,但我更喜欢用python编写,并使用transcrypt将其编译为javascript。这就足够清楚了。)
首先启用jquery用法,使
1 | __pragma__ ('alias', 'S', '$') |
定义一个返回承诺的函数,在本例中是Ajax调用:
1 2 3 4 5 6 7 | 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() |
将异步代码用作同步代码:
1 2 3 4 5 6 | async def readALot(): try: result1 = await read("url_1") result2 = await read("url_2") except Exception: console.warn("Reading a lot failed") |
使用ES2017,您应该将其作为功能声明
1 2 3 4 | async function foo() { var response = await $.ajax({url: '...'}) return response; } |
像这样执行。
1 2 3 4 5 6 | (async function() { try { var result = await foo() console.log(result) } catch (e) {} })() |
或承诺语法
1 2 3 4 5 6 7 | foo().then(response => { console.log(response) }).catch(error => { console.log(error) }) |
我们先看看森林再看看树。
这里有许多信息丰富的答案,其中的细节非常详细,我不会再重复这些答案。在javascript中编程的关键是首先要有正确的总体执行的心理模型。
好消息是,如果你很好地理解这一点,你就不必担心比赛条件。首先,您应该了解如何将代码组织为对不同离散事件的响应,以及如何将它们串联到逻辑序列中。您可以使用Promises或更高级别的New Async/Wait作为实现这一目标的工具,或者您也可以滚动自己的工具。
但在您熟悉实际问题域之前,不应该使用任何战术工具来解决问题。绘制这些依赖关系的映射,以了解什么时候需要运行。尝试一种特别的方法来处理所有这些回调只是不会很好地为您服务。
与向您抛出代码不同,有两个概念是理解JS如何处理回调和异步性的关键。(这是一个词吗?)
事件循环和并发模型您需要注意三件事:队列、事件循环和堆栈
从广义上讲,事件循环与项目管理器类似,它不断地监听希望运行的任何函数,并在队列和堆栈之间进行通信。
1 2 3 | while (queue.waitForMessage()) { queue.processNextMessage(); } |
一旦它接收到运行某个东西的消息,就会将它添加到队列中。队列是等待执行的内容的列表(如Ajax请求)。想象一下:
1 2 3 | 1. call foo.com/api/bar using foobarFunc 2. Go perform an infinite loop ... and so on |
当其中一条消息要执行时,它会从队列中弹出消息并创建一个堆栈,堆栈就是JS执行消息中的指令所需执行的一切。所以在我们的例子中,它被告知调用
1 2 3 | function foobarFunc (var) { console.log(anotherFunction(var)); } |
因此,foobarfunc需要执行的任何操作(在我们的示例中是
这里的关键是执行顺序。那就是
什么时候有东西跑当您使用Ajax对外部方进行调用或运行任何异步代码(例如setTimeout)时,javascript在继续之前依赖于响应。
最大的问题是什么时候能得到答复?答案是我们不知道-所以事件循环在等待消息说"嘿,跑我"。如果JS只是同步地等待这个消息,那么你的应用程序就会冻结,并且会很糟糕。所以JS继续执行队列中的下一个项目,同时等待消息被添加回队列。
这就是为什么对于异步功能,我们使用称为回调的东西。这有点像字面上的承诺。正如我承诺在某个时候返回某些内容一样,jquery使用特定的回调,称为
因此,您需要做的是传递一个函数,该函数被承诺在某个时刻用传递给它的数据执行。
因为回调不是立即执行的,而是在以后的某个时间执行的,所以必须将引用传递给未执行的函数。所以
1 2 3 | function foo(bla) { console.log(bla) } |
所以大多数时候(但不总是)你会通过
希望这会有点道理。当您遇到这样看起来令人困惑的事情时,我强烈建议您充分阅读文档,至少了解一下它。它将使您成为更好的开发人员。
使用承诺
这个问题最完美的答案是使用
1 2 3 4 5 6 7 8 9 10 11 | 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); }); } |
用法
1 2 3 4 5 6 | ajax("GET","/test","acrive=1").then(function(result) { // Code depending on result }) .catch(function() { // An error occurred }); |
但是等等…
使用承诺有问题!
为什么我们要使用我们自己的习俗承诺?我使用这个解决方案有一段时间了,直到我发现旧浏览器中有一个错误:
1 | Uncaught ReferenceError: Promise is not defined |
所以我决定实现我自己的ES3承诺类,如果没有定义的话,它将低于JS编译器。只需在主代码之前添加此代码,然后安全用户承诺!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 2 3 4 5 6 7 8 9 10 11 12 13 | 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)) |
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
或者,
1 2 3 | getData(ajaxurl).then(function(res) { console.log(res) } |
将节点上的XHR转换为异步等待的简单代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); } })(); |