如何从异步调用返回响应?
如何从发出异步请求的函数 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`
}
使用 < code>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`
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
我会用一幅看起来很可怕的手绘漫画来回答。第二张图片是代码示例中
result
为undefined
的原因。I will answer with a horrible-looking, hand-drawn comic. The second image is the reason why
result
isundefined
in your code example.最简单的解决方案是创建一个 JavaScript 函数并为 Ajax
success
回调调用它。The simplest solution is to create a JavaScript function and call it for the Ajax
success
callback.这里的大多数答案都为您进行单个异步操作时提供了有用的建议,但有时,当您需要对数组或其他类似列表的结构中的每个条目执行异步操作时,就会出现这种情况。这样做的诱惑是:
示例:
不起作用的原因是,当您尝试使用结果时,来自
doSomethingAsync
的回调尚未运行。因此,如果您有一个数组(或某种列表)并且想要对每个条目执行异步操作,您有两个选择:并行(重叠)或串行(按顺序一个接一个)执行操作。
并行
您可以启动所有这些并跟踪您期望的回调数量,然后在收到那么多回调时使用结果:
示例:
(我们可以取消
expecting
并仅使用results.length === theArray.length
,但这让我们有可能theArray
在调用未完成时发生更改...)请注意我们如何使用
forEach
中的index
将结果保存在results 中
位于与其相关的条目相同的位置,即使结果到达时无序(因为异步调用不一定按照启动的顺序完成)。但是,如果您需要从函数返回这些结果怎么办?正如其他答案所指出的,你不能;你必须让你的函数接受并调用回调(或返回
示例:
或者这里有一个返回
Promise
的版本:当然,如果
doSomethingAsync
向我们传递了错误,我们会使用reject
来拒绝当我们遇到错误时承诺。)示例:
(或者,您可以为返回承诺的
doSomethingAsync
制作一个包装器,然后执行以下操作...)如果
doSomethingAsync
为您提供Promise,您可以使用Promise.all
:如果您知道
doSomethingAsync
将忽略第二个和第三个参数,则可以将其直接传递给map
(map
使用以下命令调用其回调三个参数,但大多数人只使用大多数时候是第一次):示例:
请注意,
Promise.all
会使用您给出的所有承诺的结果数组来解析其承诺(当这些承诺全部得到解决时),或者当 第一个你给它的承诺却被它拒绝。系列
假设您不希望操作并行?如果您想依次运行它们,则需要等待每个操作完成才能开始下一个操作。下面是一个函数的示例,该函数执行此操作并使用结果调用回调:
(由于我们正在连续进行工作,因此我们可以只使用
results.push(result)
因为我们知道我们不会得到乱序的结果。在上面我们可以使用results[index] = result;
,但在下面的一些示例中我们没有可用的索引。 )示例:
(或者,再次为
doSomethingAsync
构建一个包装器,为您提供 Promise 并执行以下操作...)如果
doSomethingAsync
为您提供 Promise ,如果你可以使用 ES2017+ 语法(也许使用像 Babel 这样的转译器),你可以使用async
函数 带有 < a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of" rel="noreferrer">for-of
和await
:示例:
如果您还不能使用 ES2017+ 语法,则可以使用 “Promise reduce”模式(这比通常的 Promise reduce 更复杂,因为我们没有将结果从一个传递到下一个,而是将它们的结果收集在一个数组中):
例子:
...使用 ES2015+ 箭头函数:
示例:
Most of the answers here give useful suggestions for when you have a single async operation, but sometimes, this comes up when you need to do an asynchronous operation for each entry in an array or other list-like structure. The temptation is to do this:
Example:
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:
Example:
(We could do away with
expecting
and just useresults.length === theArray.length
, but that leaves us open to the possibility thattheArray
is changed while the calls are outstanding...)Notice how we use the
index
fromforEach
to save the result inresults
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:
Example:
Or here's a version returning a
Promise
instead:Of course, if
doSomethingAsync
passed us errors, we'd usereject
to reject the promise when we got an error.)Example:
(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 usePromise.all
:If you know that
doSomethingAsync
will ignore a second and third argument, you can just pass it directly tomap
(map
calls its callback with three arguments, but most people only use the first most of the time):Example:
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:
(Since we're doing the work in series, we can just use
results.push(result)
since we know we won't get results out of order. In the above we could have usedresults[index] = result;
, but in some of the following examples we don't have an index to use.)Example:
(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 anasync
function withfor-of
andawait
:Example:
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):
Example:
...which is less cumbersome with ES2015+ arrow functions:
Example:
Angular 1
使用 AngularJS 的人可以使用承诺来处理这种情况。
这里它说,
您也可以在此处找到一个很好的解释。
在下面提到的文档中找到了一个示例。
Angular 2 及更高版本
在 Angular 2 中,请查看以下示例,但其 建议在 Angular 2 中使用可观察。
您可以通过这种方式使用它,
请参阅原文在这里发布。但是 TypeScript 不支持原生 ES6 Promises,如果你想使用它,你可能需要插件为此。
此外,这里是promises 规范。
Angular 1
People who are using AngularJS, can handle this situation using promises.
Here it says,
You can find a nice explanation here also.
An example found in documentation mentioned below.
Angular 2 and later
In Angular 2 with look at the following example, but its recommended to use observables with Angular 2.
You can consume that in this way,
See the original post here. But TypeScript does not support native ES6 Promises, if you want to use it, you might need plugin for that.
Additionally, here is the promises specification.
看一下这个例子:
正如你所看到的,
getJoke
正在返回已解决的promise(当它被解决时返回res.data.value
)。因此,您等到 $http.get 请求完成,然后执行 console.log(res.joke) (作为正常的异步流程)。这是 plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6 方式(异步 - 等待)
Have a look at this example:
As you can see
getJoke
is returning a resolved promise (it is resolved when returningres.data.value
). So you wait until the $http.get request is completed and then console.log(res.joke) is executed (as a normal asynchronous flow).This is the plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6 way (async - await)
这是许多新 JavaScript 框架中使用的双向数据绑定或存储概念非常适合您的地方之一......
所以如果您使用< a href="https://en.wikipedia.org/wiki/Angular_(web_framework)" rel="noreferrer">Angular, React,或任何其他执行双向数据绑定或存储概念的框架,这个问题已经为您解决了,所以简单来说,您的结果是
undefined
在第一阶段,所以在收到数据之前你已经得到了result = undefined
,那么一旦你得到结果,它就会被更新并被分配给你的响应的新值Ajax 调用...但是如何用纯 JavaScript 实现它或 jQuery 例如,正如您在这个问题中所问的那样?
您可以使用回调、promise 和最近可观察到的来为您处理它。例如,在 Promise 中,我们有一些诸如
success()
或then()
之类的函数,这些函数将在您的数据准备就绪时执行。与可观察对象上的回调或订阅函数相同。例如,在您使用 jQuery 的情况下,您可以执行以下操作:
有关更多信息,请研究 Promise 和 Observables,它们是执行异步操作的较新方法。
This is one of the places which two-way data binding or store concept that's used in many new JavaScript frameworks will work great for you...
So if you are using Angular, React, or any other frameworks which do two-way data binding or store concept, this issue is simply fixed for you, so in easy words, your result is
undefined
at the first stage, so you have gotresult = undefined
before you receive the data, then as soon as you get the result, it will be updated and get assigned to the new value which response of your Ajax call...But how you can do it in pure JavaScript or jQuery for example as you asked in this question?
You can use a callback, promise and recently observable to handle it for you. For example, in promises we have some function like
success()
orthen()
which will be executed when your data is ready for you. The same with callback or the subscribe function on an observable.For example, in your case which you are using jQuery, you can do something like this:
For more information, study promises and observables which are newer ways to do this async stuff.
这是我们在探索 JavaScript 的“奥秘”时遇到的一个非常常见的问题。今天就让我来揭开这个谜团吧。
让我们从一个简单的 JavaScript 函数开始:
这是一个简单的同步函数调用(其中每一行代码在下一行代码之前“完成其工作”),结果与预期相同。
现在让我们添加一些扭曲,在我们的函数中引入一点延迟,以便所有代码行都不会按顺序“完成”。因此,它将模拟该函数的异步行为:
所以你就可以了;这种延迟破坏了我们预期的功能!但究竟发生了什么?嗯,如果你看一下代码,这实际上是非常合乎逻辑的。
函数
foo()
在执行时不返回任何内容(因此返回值为undefined
),但它确实启动了一个计时器,该计时器在 1 秒后执行一个函数以返回 '哇哦。但正如您所看到的,分配给 bar 的值是 foo() 立即返回的内容,它什么也没有,即只是undefined
。那么,我们如何解决这个问题?
让我们向我们的函数请求一个承诺。
Promise 的真正含义是:它意味着该函数保证您提供它将来获得的任何输出。因此,让我们看看它在上面的小问题中的实际应用:
因此,总结是 - 要处理异步函数,例如基于 Ajax 的调用等,您可以使用 Promise 来
解析
值(其中你打算回来)。因此,简而言之,您在异步函数中解析值而不是返回。更新(带有 async/await 的 Promises)
除了使用 then/catch 来处理 Promise 之外,还存在另一种方法。这个想法是识别异步函数,然后等待承诺解析,然后再移动到下一行代码。它仍然只是底层的承诺,但具有不同的语法方法。为了让事情更清楚,你可以在下面找到一个比较:
then/catch 版本:
async/await 版本:
It's a very common issue we face while struggling with the 'mysteries' of JavaScript. Let me try demystifying this mystery today.
Let's start with a simple JavaScript function:
That's a simple synchronous function call (where each line of code is 'finished with its job' before the next one in sequence), and the result is same as expected.
Now let's add a bit of twist, by introducing a little delay in our function, so that all lines of code are not 'finished' in sequence. Thus, it will emulate the asynchronous behavior of the function:
So there you go; that delay just broke the functionality we expected! But what exactly happened? Well, it's actually pretty logical if you look at the code.
The function
foo()
, upon execution, returns nothing (thus returned value isundefined
), but it does start a timer, which executes a function after 1 second to return 'wohoo'. But as you can see, the value that's assigned to bar is the immediately returned stuff from foo(), which is nothing, i.e., justundefined
.So, how do we tackle this issue?
Let's ask our function for a promise.
Promise is really about what it means: it means that the function guarantees you to provide with any output it gets in future. So let's see it in action for our little problem above:
Thus, the summary is - to tackle the asynchronous functions like Ajax-based calls, etc., you can use a promise to
resolve
the value (which you intend to return). Thus, in short you resolve value instead of returning, in asynchronous functions.UPDATE (Promises with async/await)
Apart from using
then/catch
to work with promises, there exists one more approach. The idea is to recognize an asynchronous function and then wait for the promises to resolve, before moving to the next line of code. It's still just thepromises
under the hood, but with a different syntactical approach. To make things clearer, you can find a comparison below:then/catch version:
async/await version:
从异步函数返回值的另一种方法是传入一个将存储异步函数结果的对象。
这是一个相同的示例:
我使用
result
对象在异步操作期间存储值。这使得即使在异步作业之后结果也可用。我经常使用这种方法。我很想知道这种方法在涉及通过连续模块将结果连接回来的情况下效果如何。
Another approach to return a value from an asynchronous function, is to pass in an object that will store the result from the asynchronous function.
Here is an example of the same:
I am using the
result
object to store the value during the asynchronous operation. This allows the result be available even after the asynchronous job.I use this approach a lot. I would be interested to know how well this approach works where wiring the result back through consecutive modules is involved.
虽然 Promise 和回调在许多情况下都工作得很好,但表达类似这样的内容是很痛苦的:
你最终会经历
async1
;检查name
是否未定义并相应地调用回调。虽然在小示例中没问题,但当您有很多类似的情况并涉及错误处理时,就会变得烦人。
Fibers 有助于解决这个问题。
您可以在此处查看该项目。
While promises and callbacks work fine in many situations, it is a pain in the rear to express something like:
You'd end up going through
async1
; check ifname
is undefined or not and call the callback accordingly.While it is okay in small examples it gets annoying when you have a lot of similar cases and error handling involved.
Fibers
helps in solving the issue.You can checkout the project here.
我编写的以下示例展示了如何
这个工作示例是独立的。它将定义一个简单的请求对象,该对象使用窗口
XMLHttpRequest
对象进行调用。它将定义一个简单的函数来等待一堆承诺完成。语境。该示例正在查询 Spotify Web API 端点以搜索
playlist 对象:
对于每个项目,一个新的 Promise 将触发一个块 -
ExecutionBlock
,解析结果,根据结果安排一组新的 Promise数组,即 Spotify 的列表user
对象并在ExecutionProfileBlock
中异步执行新的 HTTP 调用。然后,您可以看到一个嵌套的 Promise 结构,它允许您生成多个完全异步的嵌套 HTTP 调用,并通过
Promise.all
连接每个调用子集的结果。注意
最近的 Spotify
search
API 将要求在请求标头中指定访问令牌:因此,要运行以下示例,您需要将访问令牌放入请求标头中:
我已经广泛讨论了这个解决方案
The following example I have written shows how to
This working example is self-contained. It will define a simple request object that uses the window
XMLHttpRequest
object to make calls. It will define a simple function to wait for a bunch of promises to be completed.Context. The example is querying the Spotify Web API endpoint in order to search for
playlist
objects for a given set of query strings:For each item, a new Promise will fire a block -
ExecutionBlock
, parse the result, schedule a new set of promises based on the result array, that is a list of Spotifyuser
objects and execute the new HTTP call within theExecutionProfileBlock
asynchronously.You can then see a nested Promise structure, that lets you spawn multiple and completely asynchronous nested HTTP calls, and join the results from each subset of calls through
Promise.all
.NOTE
Recent Spotify
search
APIs will require an access token to be specified in the request headers:So, you to run the following example you need to put your access token in the request headers:
I have extensively discussed this solution here.
简短的答案是,您必须实现这样的回调:
The short answer is, you have to implement a callback like this:
浏览器可以分为三个部分:
事件循环
Web API
事件队列
事件循环永远运行,即一种无限循环。事件队列是在某个事件(例如:单击)上推送所有功能的地方。
这是一个一个地从队列中执行并放入事件循环中,该事件循环执行该函数并在执行第一个函数后为下一个函数做好准备。这意味着只有在事件循环中执行队列中该函数之前的函数后,才会开始执行一个函数。
现在让我们假设我们将两个函数放入队列中。一种是从服务器获取数据,另一种是利用该数据。我们首先将 serverRequest() 函数推入队列,然后将 utiliseData() 函数推入队列。 serverRequest 函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据需要多长时间,因此此过程预计需要时间,因此我们忙于事件循环,从而挂起页面。
这就是 Web API 发挥作用的地方。它从事件循环中获取此函数并与服务器进行处理,使事件循环空闲,以便我们可以执行队列中的下一个函数。
队列中的下一个函数是 utiliseData(),它会进入循环,但由于没有可用数据,它会被浪费,并且下一个函数的执行将继续,直到队列末尾。 (这称为异步调用,即,在获得数据之前我们可以做其他事情。)
让我们假设我们的 serverRequest() 函数在代码中有一个 return 语句。当我们从服务器Web API取回数据时,它会将其推送到队列末尾的队列中。
当它被推到队列末尾时,我们无法利用它的数据,因为我们的队列中没有任何函数可以利用这些数据。 因此不可能从异步调用中返回某些内容。
因此,解决方案是回调或承诺 。
我们将我们的函数(利用从服务器返回的数据的函数)提供给调用服务器的函数。
在我的代码中,它被称为:
JavaScript.info 回调
The browser can be divided into three parts:
Event Loop
Web API
Event Queue
The event loop runs for forever, i.e., kind of an infinite loop. The event queue is where all your functions are pushed on some event (example: click).
This is one by one carried out of queue and put into the event loop which executes this function and prepares itself for the next one after the first one is executed. This means execution of one function doesn't start until the function before it in the queue is executed in the event loop.
Now let us think we pushed two functions in a queue. One is for getting a data from the server and another utilises that data. We pushed the serverRequest() function in the queue first and then the utiliseData() function. The serverRequest function goes in the event loop and makes a call to server as we never know how much time it will take to get data from server, so this process is expected to take time and so we busy our event loop thus hanging our page.
That's where Web API come into the role. It takes this function from the event loop and deals with the server making the event loop free, so that we can execute the next function from the queue.
The next function in the queue is utiliseData() which goes in the loop, but because of no data available, it goes to waste and execution of the next function continues until the end of the queue. (This is called Async calling, i.e., we can do something else until we get data.)
Let us suppose our serverRequest() function had a return statement in code. When we get back data from the server Web API, it will push it in the queue at the end of queue.
As it gets pushed at the end of the queue, we cannot utilise its data as there isn't any function left in our queue to utilise this data. Thus it is not possible to return something from the async call.
Thus the solution to this is callback or promise.
We give our function (function utilising data returned from the server) to a function calling the server.
In my code it is called as:
JavaScript.info callback
2017 年答案:您现在可以在当前的每个浏览器和 Node.js 中完全执行您想要的操作
这非常简单:
这是代码的工作版本:
await 在所有当前浏览器和 Node.js 8 中均受支持
2017 answer: you can now do exactly what you want in every current browser and Node.js
This is quite simple:
Here's a working version of your code:
await is supported in all current browsers and Node.js 8
您可以使用这个自定义库(使用 Promise 编写)进行远程调用。
简单使用示例:
You can use this custom library (written using Promise) to make a remote call.
Simple usage example:
另一种解决方案是通过顺序执行器 nsynjs 执行代码。
如果底层函数是 promisified,
nsynjs 将按顺序评估所有 Promise,并将 Promise 结果放入
data
属性中:如果底层函数没有 promisified
步骤 1. 将带有回调的函数包装到 nsynjs 感知的包装器中(如果它有 promisified 版本,则可以跳过此步骤):
步骤 2. 将同步逻辑放入函数中:
步骤 3. 运行通过 nsynjs 以同步方式运行函数:
Nsynjs 将逐步计算所有运算符和表达式,如果某些慢速函数的结果尚未准备好,则暂停执行。
更多示例位于此处。
Another solution is to execute code via the sequential executor nsynjs.
If the underlying function is promisified
nsynjs will evaluate all promises sequentially, and put the promise result into the
data
property: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):
Step 2. Put synchronous logic into function:
Step 3. Run function in synchronous manner via nsynjs:
Nsynjs will evaluate all operators and expressions step-by-step, pausing execution in case if the result of some slow function is not ready.
More examples are here.
1. 第一步绊脚石
对于其他许多人来说,我遇到的异步调用一开始是令人困惑的。
我不记得细节,但我可能尝试过类似的事情:
哎呀!
该行的输出
console.log('最后结果:' + result);
我认为会在最后打印,但在其他输出之前打印!
– 并且它不包含结果:它只是打印
undefined
。 1怎么会?
一个有用的见解
我清楚地记得我第一次关于异步调用的顿悟 (
1. A first stumbling step
As for many others, my encounter with asynchronous calls was puzzling at first.
I don't remember the details, but I may have tried something like:
Whoops!
The output of the line
console.log('Finally, the result: ' + result);
which I thought would be printed last, is printed before the other output!
– And it doesn't contain the result: it just prints
undefined
. 1How come?
A helpful insight
I distinctly remember my first aha (????) moment about asynchronous calls.
It was :
This is true in the example above.
2. Plain JavaScript and a callback function
Luckily, it is possible to write code after the asynchronous call that deals with the response once it has completed.
One alternative is the use of a callback function in a continuation-passing style :
3
Note how the function
asynchronousFunc
isvoid
. It returns nothing.asynchronousFunc
is called with an anonymous callback function,(
asynchronousFunc(function (result) {...});
).This executes the desired actions on the result after the request has completed – when the
responseText
is available.Running the above snippet shows how I will probably not want to write any code after the asynchronous call (such as the line
LAST in the code, but executed FIRST!
).Why? – Because such code will run before the asynchronous call delivers any response data.
Doing so is bound to cause confusion when comparing the code with the output.
3. Promise with
.then()
The
.then()
construct was introduced in the ECMA-262 6th Edition in June 2015.The code below is plain JavaScript, replacing the old-school XMLHttpRequest with Fetch. 4
4. Promise with
async
/await
The
async
/await
construct was introduced in the ECMA-262 8th Edition in June 2017.A word of warning is warranted if you decide to go with the
async
/await
construct.Note in the above snippet how
await
is needed in two places.If forgotten in the first place, there will be no output at all.
If forgotten in the second place, the only output will be
Using async/await:
– nothing else gets printed.Forgetting the
async
prefix of the function is maybe the worst of all – you'll get a"SyntaxError"
– and likely no hint about the missingasync
keyword.All the above examples succinctly convey how asynchronous calls may be used on toyish APIs. 5
References
async
/await
1
Expressed by the asker of the question as they all return
undefined
.2
Here is more on how asynchronous calls may be confusing at first.
3
Like the X in AJAX, the name
XMLHttpRequest
is misleading – it can be used to retrieve any type of data, not just XML.These days, the data format of Web APIs is ubiquitously JSON, not XML.
4
Fetch returns a Promise.
I was surprised to learn that neither XMLHttpRequest nor Fetch are part of the ECMAScript standard.
The reason JavaScript can access them here is that the web browser provides them.
The Fetch Standard and the XMLHttpRequest Standard are both upheld by the Web Hypertext Application Technology Working Group which was formed in June 2004. \
5
You might also be interested in
How can I fetch an array of URLs with Promise.all?.
ECMAScript 6 具有“生成器”,可让您轻松地以异步方式进行编程。
要运行上面的代码,你可以这样做:
如果你需要定位不支持 ES6 的浏览器,你可以通过 Babel 或closure-compiler运行代码来生成 ECMAScript 5。
回调
...args
包装在一个数组中,并在您读取它们时进行解构,以便该模式可以处理具有多个参数的回调。例如 node fs:ECMAScript 6 has 'generators' which allow you to easily program in an asynchronous style.
To run the above code you do this:
If you need to target browsers that don't support ES6 you can run the code through Babel or closure-compiler to generate ECMAScript 5.
The callback
...args
are wrapped in an array and destructured when you read them so that the pattern can cope with callbacks that have multiple arguments. For example with node fs:我们发现自己处于一个似乎沿着我们称为“时间”的维度前进的宇宙中。我们并不真正理解时间是什么,但我们已经发展了抽象概念和词汇,让我们能够推理和谈论它:“过去”,“现在”,“未来”,“之前”,“之后”。
我们构建的计算机系统越来越多地将时间作为一个重要维度。某些事情是注定要在未来发生的。在这些第一件事最终发生之后,还需要发生其他事情。这是称为“异步性”的基本概念。在我们日益网络化的世界中,最常见的异步情况是等待某些远程系统响应某些请求。
考虑一个例子。你打电话给送奶工并点了一些牛奶。当它到来时,您想将其放入咖啡中。你现在不能把牛奶加到咖啡里,因为它还没有到。您必须等待它到来才能将其放入咖啡中。换句话说,以下内容将不起作用:
因为 JavaScript 无法知道它需要等待
order_milk
完成才能执行put_in_coffee.换句话说,它不知道
order_milk
是异步——直到将来某个时间才会产生牛奶。 JavaScript 和其他声明性语言执行一条又一条语句,无需等待。解决此问题的经典 JavaScript 方法是利用 JavaScript 支持将函数作为可传递的第一类对象这一事实,将函数作为参数传递给异步请求,然后在异步请求完成时调用该函数它的任务是在未来的某个时候。这就是“回调”方法。它看起来像这样:
order_milk
启动,订购牛奶,然后,当且仅当牛奶到达时,它调用put_in_coffee
。这种回调方法的问题在于,它污染了用
return
报告其结果的函数的正常语义;相反,函数不得通过调用作为参数给出的回调来报告其结果。此外,在处理较长的事件序列时,这种方法很快就会变得笨拙。举个例子,假设我要等牛奶放入咖啡中,然后才执行第三步,即喝咖啡。我最终需要写这样的东西:我传递给
put_in_coffee
的地方是要放入的牛奶,以及一旦牛奶要执行的操作 (drink_coffee
)这样的代码变得难以编写、阅读和调试。在这种情况下,我们可以将问题中的代码重写为:
输入承诺
这是“承诺”概念的动机,“承诺”是一种特殊类型的值,代表未来或某种>异步结果。它可以代表已经发生的事情,或者将来会发生的事情,或者可能永远不会发生的事情。 Promise 有一个名为
then
的方法,您可以向该方法传递一个要在 Promise 表示的结果实现时执行的操作。对于牛奶和咖啡,我们设计
order_milk
来返回牛奶到达的承诺,然后将put_in_coffee
指定为then
操作,如下所示:这样做的一个优点是我们可以将它们串在一起以创建未来发生的序列(“链接”):
让我们将 Promise 应用于您的特定问题。我们将把请求逻辑包装在一个函数中,该函数返回一个承诺:
实际上,我们所做的就是在对
$.ajax
的调用中添加一个return
。这是有效的,因为 jQuery 的$.ajax
已经返回了一种类似承诺的东西。 (实际上,在不深入细节的情况下,我们更愿意包装此调用以返回真正的承诺,或者使用$.ajax
的替代方案来实现这一点。)现在,如果我们想要加载文件并等待其完成,然后执行某些操作,我们可以简单地说,例如,
当使用 Promise 时,我们最终会将大量函数传递给
then
,因此使用 more 通常会很有帮助 :紧凑的 ES6 风格的箭头函数
async
关键字但是,对于必须以一种方式(如果同步)编写代码而如果异步则必须以完全不同的方式编写代码,仍然存在一些隐约的不满。对于同步,我们编写,
但如果
a
是异步的,则我们必须编写 Promise上面,我们说,“JavaScript 无法知道它需要等待在执行第二个之前先调用完成”。如果有某种方式告诉 JavaScript 这不是很好吗?事实证明,有 --await 关键字,在称为“异步”函数的特殊类型函数中使用。此功能是即将推出的 ECMAScript (ES) 版本的一部分,但它已经在转译器中可用,例如 Babel 给出正确的预设。这使我们可以简单地写
在您的情况下,您可以写类似的内容
We find ourselves in a universe which appears to progress along a dimension we call "time". We don't really understand what time is, but we have developed abstractions and vocabulary that let us reason and talk about it: "past", "present", "future", "before", "after".
The computer systems we build--more and more--have time as an important dimension. Certain things are set up to happen in the future. Then other things need to happen after those first things eventually occur. This is the basic notion called "asynchronicity". In our increasingly networked world, the most common case of asynchronicity is waiting for some remote system to respond to some request.
Consider an example. You call the milkman and order some milk. When it comes, you want to put it in your coffee. You can't put the milk in your coffee right now, because it is not here yet. You have to wait for it to come before putting it in your coffee. In other words, the following won't work:
Because JavaScript has no way to know that it needs to wait for
order_milk
to finish before it executesput_in_coffee
. In other words, it does not know thatorder_milk
is asynchronous--is something that is not going to result in milk until some future time. JavaScript, and other declarative languages execute one statement after another without waiting.The classic JavaScript approach to this problem, taking advantage of the fact that JavaScript supports functions as first-class objects which can be passed around, is to pass a function as a parameter to the asynchronous request, which it will then invoke when it has completed its task sometime in the future. That is the "callback" approach. It looks like this:
order_milk
kicks off, orders the milk, then, when and only when it arrives, it invokesput_in_coffee
.The problem with this callback approach is that it pollutes the normal semantics of a function reporting its result with
return
; instead, functions must not reports their results by calling a callback given as a parameter. Also, this approach can rapidly become unwieldy when dealing with longer sequences of events. For example, let's say that I want to wait for the milk to be put in the coffee, and then and only then perform a third step, namely drinking the coffee. I end up needing to write something like this:where I am passing to
put_in_coffee
both the milk to put in it, and also the action (drink_coffee
) to execute once the milk has been put in. Such code becomes hard to write, and read, and debug.In this case, we could rewrite the code in the question as:
Enter promises
This was the motivation for the notion of a "promise", which is a particular type of value which represents a future or asynchronous outcome of some sort. It can represent something that already happened, or that is going to happen in the future, or might never happen at all. Promises have a single method, named
then
, to which you pass an action to be executed when the outcome the promise represents has been realized.In the case of our milk and coffee, we design
order_milk
to return a promise for the milk arriving, then specifyput_in_coffee
as athen
action, as follows:One advantage of this is that we can string these together to create sequences of future occurrences ("chaining"):
Let's apply promises to your particular problem. We will wrap our request logic inside a function, which returns a promise:
Actually, all we've done is added a
return
to the call to$.ajax
. This works because jQuery's$.ajax
already returns a kind of promise-like thing. (In practice, without getting into details, we would prefer to wrap this call so as for return a real promise, or use some alternative to$.ajax
that does so.) Now, if we want to load the file and wait for it to finish and then do something, we can simply sayfor instance,
When using promises, we end up passing lots of functions into
then
, so it's often helpful to use the more compact ES6-style arrow functions:The
async
keywordBut there's still something vaguely dissatisfying about having to write code one way if synchronous and a quite different way if asynchronous. For synchronous, we write
but if
a
is asynchronous, with promises we have to writeAbove, we said, "JavaScript has no way to know that it needs to wait for the first call to finish before it executes the second". Wouldn't it be nice if there was some way to tell JavaScript that? It turns out that there is--the
await
keyword, used inside a special type of function called an "async" function. This feature is part of the upcoming version of ECMAScript (ES), but it is already available in transpilers such as Babel given the right presets. This allows us to simply writeIn your case, you would be able to write something like
以下是处理异步请求的一些方法:
示例:jQuery 延迟实现与...一起工作多个请求
Here are some approaches to work with asynchronous requests:
Example: jQuery deferred implementation to work with multiple requests
简短回答:您的
foo()
方法立即返回,而$ajax()
调用在函数返回后异步执行。那么问题是异步调用返回后如何或在哪里存储它检索到的结果。该线程中给出了几种解决方案。也许最简单的方法是将一个对象传递给 foo() 方法,并在异步调用完成后将结果存储在该对象的成员中。
请注意,对 foo() 的调用仍然不会返回任何有用的内容。但是,异步调用的结果现在将存储在
result.response
中。Short answer: Your
foo()
method returns immediately, while the$ajax()
call executes asynchronously after the function returns. The problem is then how or where to store the results retrieved by the async call once it returns.Several solutions have been given in this thread. Perhaps the easiest way is to pass an object to the
foo()
method, and to store the results in a member of that object after the async call completes.Note that the call to
foo()
will still return nothing useful. However, the result of the async call will now be stored inresult.response
.在
foo()
success 中使用callback()
函数。按这个方法试试吧。它简单易懂。
Use a
callback()
function inside thefoo()
success.Try it in this way. It is simple and easy to understand.
使用 Promise
这个问题最完美的答案是使用 Promise。
用法
但是等等...!
使用promise有问题!
为什么我们应该使用自己自定义的 Promise?
我使用这个解决方案有一段时间,直到我发现旧浏览器中存在错误:
因此,如果未定义,我决定为 ES3 以下 JavaScript 编译器实现我自己的 Promise 类。只需在主代码之前添加此代码,然后安全地使用 Promise!
Using Promise
The most perfect answer to this question is using
Promise
.Usage
But wait...!
There is a problem with using promises!
Why should we use our own custom Promise?
I was using this solution for a while until I figured out there is an error in old browsers:
So I decided to implement my own Promise class for ES3 to below JavaScript compilers if it's not defined. Just add this code before your main code and then safely use Promise!
使用 ES2017 你应该将 this 作为函数声明。
并像这样执行它。
或者 Promise 语法。
演示上述代码的堆栈片段。
Using ES2017 you should have this as the function declaration.
And executing it like this.
Or the Promise syntax.
Stack Snippet that demonstrates the code above.
当然有很多方法,比如同步请求、promise,但根据我的经验,我认为你应该使用回调方法。 JavaScript 的异步行为是很自然的。
因此,您的代码片段可以重写为有点不同:
Of course there are many approaches like synchronous request, promise, but from my experience I think you should use the callback approach. It's natural to asynchronous behavior of JavaScript.
So, your code snippet can be rewritten to be a little different:
问题是:
其中可以解释为:
解决方案是避免回调,并结合使用 Promises 和 async/await。
我想举一个 Ajax 请求的例子。
(虽然可以用 JavaScript 编写,但我更喜欢用 Python 编写,并使用 Transcrypt 就足够清楚了。)
首先让我们启用 jQuery,让
$
可以用作S
:定义一个返回 Promise 的函数。 >,在本例中是 Ajax调用:
使用异步代码,就好像它是同步:
The question was:
which can be interpreted as:
The solution will be to avoid callbacks, and use a combination of Promises and async/await.
I would like to give an example for an Ajax request.
(Although it can be written in JavaScript, I prefer to write it in Python, and compile it to JavaScript using Transcrypt. It will be clear enough.)
Let’s first enable jQuery usage, to have
$
available asS
:Define a function which returns a Promise, in this case an Ajax call:
Use the asynchronous code as if it were synchronous:
问题
Ajax 代表 异步。这意味着发送请求(或者更确切地说接收响应)被从正常执行流程中删除。在您的示例中,
$.ajax
立即返回,并且下一条语句return result;
在您作为success
回调传递的函数之前执行甚至打电话。这里有一个类比,希望可以使同步流和异步流之间的区别更加清晰:
同步
想象一下,您给朋友打电话,请他为您查找一些东西。尽管这可能需要一段时间,但您仍然在打电话并凝视着太空,直到您的朋友给您所需的答案。
当您进行包含“正常”代码的函数调用时,也会发生同样的情况:
尽管
findItem
可能需要很长时间才能执行,但var item = findItem();
之后的任何代码code> 必须等待直到函数返回结果。异步
您出于同样的原因再次致电您的朋友。但这次你告诉他你很着急,他应该用你的手机给你回电。你挂断电话,离开家,做你计划做的事情。一旦你的朋友给你回电话,你就正在处理他给你的信息。
这正是您发出 Ajax 请求时所发生的情况。
不等待响应,而是立即继续执行并执行 Ajax 调用之后的语句。为了最终获得响应,您需要提供一个在收到响应后调用的函数,即回调(注意到什么了吗?回调?)。该调用之后的任何语句都会在调用回调之前执行。
解决方案
拥抱 JavaScript 的异步特性!虽然某些异步操作提供了同步操作(“Ajax”也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中。
你问为什么不好?
JavaScript 在浏览器的 UI 线程中运行,任何长时间运行的进程都会锁定 UI,使其无响应。另外,JavaScript的执行时间是有上限的,浏览器会询问用户是否继续执行。
所有这些都会导致非常糟糕的用户体验。用户将无法判断一切是否正常。此外,对于连接速度较慢的用户来说,效果会更差。
下面我们将介绍三种不同的解决方案,它们都是相互构建的:
async/await
的 Promises(ES2017+,如果您使用转译器或再生器)then() 进行承诺
(ES2015+,如果您使用众多 Promise 库之一)所有三个库都可以在当前浏览器和 Node 7+ 中使用。
ES2017+:带有
async/await
2017 年发布的 ECMAScript 版本引入了对异步函数的语法级支持。借助
async
和await
,您可以以“同步风格”编写异步。代码仍然是异步的,但更容易阅读/理解。async/await
构建在 Promise 之上:async
函数始终返回一个 Promise。await
“解开”一个 Promise,并且要么产生 Promise 被解析的值,要么在 Promise 被拒绝时抛出错误。重要提示:您只能在
async
函数或 JavaScript 模块。模块外部不支持顶级await
,因此您可能必须创建异步 IIFE (立即调用函数表达式)来启动异步
上下文(如果不使用模块)。您可以阅读有关
异步
的更多信息 和await
在 MDN 上。下面是一个详细说明上面的延迟函数
findItem()
的示例:当前浏览器 和 node 版本支持
async/await
。您还可以借助 regenerator (或使用 regenerator 的工具)将代码转换为 ES5,以支持较旧的环境,例如 Babel)。让函数接受回调
回调是指函数 1 传递给函数 2 时。函数 2 可以在函数 1 准备好时调用它。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果会传递给回调。
在问题的示例中,您可以使
foo
接受回调并将其用作success
回调。所以这就变成了
这里我们定义了“内联”函数,但你可以传递任何函数引用:
foo
本身定义如下:callback
将引用我们传递给 < code>foo 当我们调用它并将其传递给success
时。即一旦Ajax请求成功,$.ajax
将调用callback
并将响应传递给回调(可以通过result
引用) ,因为这就是我们定义回调的方式)。您还可以在将响应传递给回调之前对其进行处理:
使用回调编写代码比看起来更容易。毕竟,浏览器中的 JavaScript 很大程度上是事件驱动的(DOM 事件)。接收 Ajax 响应只不过是一个事件。
当您必须使用第三方代码时可能会出现困难,但大多数问题只需思考应用程序流程就可以解决。
ES2015+:使用 then() 进行承诺
< a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noreferrer">承诺API 是 ECMAScript 6 (ES2015) 的新功能,但它具有良好的浏览器已经支持了。还有许多库实现了标准 Promises API 并提供了其他方法来简化异步函数的使用和组合(例如,蓝鸟)。
Promise 是未来值的容器。当 Promise 收到值(已解决)或取消(拒绝)时,它会通知所有想要访问该值的“侦听器”。
与普通回调相比的优点是它们允许您解耦代码并且更容易编写。
这是使用 Promise 的示例:
应用于 Ajax 调用时,我们可以使用这样的 Promise:
描述 Promise 提供的所有优点超出了本答案的范围,但如果您编写新代码,则应该认真考虑它们。它们为您的代码提供了很好的抽象和分离。
有关 Promises 的更多信息:HTML5 rock - JavaScript Promises。
旁注:jQuery 的延迟对象
延迟对象 是 jQuery 的 Promise 自定义实现(在 Promise API 出现之前)标准化)。它们的行为几乎与 Promise 类似,但公开的 API 略有不同。
jQuery 的每个 Ajax 方法都已经返回一个“延迟对象”(实际上是延迟对象的承诺),您可以从函数中返回它:
旁注:承诺陷阱
请记住,承诺和延迟对象只是容器 /em> 对于未来值,它们不是值本身。例如,假设您有以下情况:
此代码误解了上述异步问题。具体来说,
$.ajax()
在检查服务器上的“/password”页面时不会冻结代码 - 它会向服务器发送请求,并在等待时立即返回 jQuery Ajax Deferred 对象,而不是来自服务器的响应。这意味着if
语句将始终获取此 Deferred 对象,将其视为true
,并像用户已登录一样继续进行。这不好。但修复方法很简单:
不推荐:同步“Ajax”调用
正如我所提到的,某些(!)异步操作具有同步对应操作。我不提倡使用它们,但为了完整起见,以下是执行同步调用的方法:
没有 jQuery
如果直接使用
XMLHttpRequest
对象,将false
作为第三个参数传递给.open
。jQuery
如果您使用 jQuery,您可以将
async
选项设置为假
。请注意,自 jQuery 1.8 起,此选项已被弃用。然后,您仍然可以使用
success
回调或访问 jqXHR 对象:如果您使用任何其他 jQuery Ajax 方法,例如
$.get
、$.getJSON
等。 ,你必须改变它到$.ajax
(因为您只能将配置参数传递给$.ajax
)。注意! 无法发出同步 JSONP 请求。 JSONP 本质上始终是异步的(这是不考虑此选项的又一个原因)。
The problem
The A in Ajax stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example,
$.ajax
returns immediately and the next statement,return result;
, is executed before the function you passed assuccess
callback was even called.Here is an analogy which hopefully makes the difference between synchronous and asynchronous flow clearer:
Synchronous
Imagine you make a phone call to a friend and ask him to look something up for you. Although it might take a while, you wait on the phone and stare into space, until your friend gives you the answer that you needed.
The same is happening when you make a function call containing "normal" code:
Even though
findItem
might take a long time to execute, any code coming aftervar item = findItem();
has to wait until the function returns the result.Asynchronous
You call your friend again for the same reason. But this time you tell him that you are in a hurry and he should call you back on your mobile phone. You hang up, leave the house, and do whatever you planned to do. Once your friend calls you back, you are dealing with the information he gave to you.
That's exactly what's happening when you do an Ajax request.
Instead of waiting for the response, the execution continues immediately and the statement after the Ajax call is executed. To get the response eventually, you provide a function to be called once the response was received, a callback (notice something? call back ?). Any statement coming after that call is executed before the callback is called.
Solution(s)
Embrace the asynchronous nature of JavaScript! While certain asynchronous operations provide synchronous counterparts (so does "Ajax"), it's generally discouraged to use them, especially in a browser context.
Why is it bad do you ask?
JavaScript runs in the UI thread of the browser and any long-running process will lock the UI, making it unresponsive. Additionally, there is an upper limit on the execution time for JavaScript and the browser will ask the user whether to continue the execution or not.
All of this results in a really bad user experience. The user won't be able to tell whether everything is working fine or not. Furthermore, the effect will be worse for users with a slow connection.
In the following we will look at three different solutions that are all building on top of each other:
async/await
(ES2017+, available in older browsers if you use a transpiler or regenerator)then()
(ES2015+, available in older browsers if you use one of the many promise libraries)All three are available in current browsers, and node 7+.
ES2017+: Promises with
async/await
The ECMAScript version released in 2017 introduced syntax-level support for asynchronous functions. With the help of
async
andawait
, you can write asynchronous in a "synchronous style". The code is still asynchronous, but it's easier to read/understand.async/await
builds on top of promises: anasync
function always returns a promise.await
"unwraps" a promise and either result in the value the promise was resolved with or throws an error if the promise was rejected.Important: You can only use
await
inside anasync
function or in a JavaScript module. Top-levelawait
is not supported outside of modules, so you might have to make an async IIFE (Immediately Invoked Function Expression) to start anasync
context if not using a module.You can read more about
async
andawait
on MDN.Here is an example that elaborates the delay function
findItem()
above:Current browser and node versions support
async/await
. You can also support older environments by transforming your code to ES5 with the help of regenerator (or tools that use regenerator, such as Babel).Let functions accept callbacks
A callback is when function 1 is passed to function 2. Function 2 can call function 1 whenever it is ready. In the context of an asynchronous process, the callback will be called whenever the asynchronous process is done. Usually, the result is passed to the callback.
In the example of the question, you can make
foo
accept a callback and use it assuccess
callback. So thisbecomes
Here we defined the function "inline" but you can pass any function reference:
foo
itself is defined as follows:callback
will refer to the function we pass tofoo
when we call it and we pass it on tosuccess
. I.e. once the Ajax request is successful,$.ajax
will callcallback
and pass the response to the callback (which can be referred to withresult
, since this is how we defined the callback).You can also process the response before passing it to the callback:
It's easier to write code using callbacks than it may seem. After all, JavaScript in the browser is heavily event-driven (DOM events). Receiving the Ajax response is nothing else but an event.
Difficulties could arise when you have to work with third-party code, but most problems can be solved by just thinking through the application flow.
ES2015+: Promises with then()
The Promise API is a new feature of ECMAScript 6 (ES2015), but it has good browser support already. There are also many libraries which implement the standard Promises API and provide additional methods to ease the use and composition of asynchronous functions (e.g., bluebird).
Promises are containers for future values. When the promise receives the value (it is resolved) or when it is canceled (rejected), it notifies all of its "listeners" who want to access this value.
The advantage over plain callbacks is that they allow you to decouple your code and they are easier to compose.
Here is an example of using a promise:
Applied to our Ajax call we could use promises like this:
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:
Side note: Promise gotchas
Keep in mind that promises and deferred objects are just containers for a future value, they are not the value itself. For example, suppose you had the following:
This code misunderstands the above asynchronous issues. Specifically,
$.ajax()
doesn't freeze the code while it checks the '/password' page on your server - it sends a request to the server and while it waits, it immediately returns a jQuery Ajax Deferred object, not the response from the server. That means theif
statement is going to always get this Deferred object, treat it astrue
, and proceed as though the user is logged in. Not good.But the fix is easy:
Not recommended: Synchronous "Ajax" calls
As I mentioned, some(!) asynchronous operations have synchronous counterparts. I don't advocate their use, but for completeness' sake, here is how you would perform a synchronous call:
Without jQuery
If you directly use a
XMLHttpRequest
object, passfalse
as third argument to.open
.jQuery
If you use jQuery, you can set the
async
option tofalse
. Note that this option is deprecated since jQuery 1.8.You can then either still use a
success
callback or access theresponseText
property of the jqXHR object:If you use any other jQuery Ajax method, such as
$.get
,$.getJSON
, etc., you have to change it to$.ajax
(since you can only pass configuration parameters to$.ajax
).Heads up! It is not possible to make a synchronous JSONP request. JSONP by its very nature is always asynchronous (one more reason to not even consider this option).
如果您没有在代码中使用 jQuery,那么这个答案适合您。
您的代码应该类似于以下内容:
Felix Kling 做得很好为使用的人编写答案jQuery for AJAX,但我决定为那些不会的人提供替代方案。
(请注意,对于那些使用新的
fetch
API、Angular 或 Promise 的用户,我在下面添加了另一个答案)您所面临的问题
这是来自其他答案的“问题解释”的简短摘要,如果您在阅读此内容后不确定,请阅读该内容。
AJAX 中的A 代表异步。这意味着发送请求(或者更确切地说接收响应)被从正常执行流程中删除。在您的示例中,
.send
立即返回,并且在调用作为
success
回调传递的函数之前执行下一条语句return result;
。这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。
这是一个简单的类比:
(Fiddle)
返回的
a
值为undefined
因为a=5
部分尚未执行。 AJAX 的行为是这样的,您在服务器有机会告诉浏览器该值是什么之前就返回了该值。此问题的一种可能的解决方案是重新主动编写代码,告诉您的程序计算完成后要做什么。
这称为CPS。基本上,我们向
getFive
传递一个要在完成时执行的操作,我们告诉我们的代码如何在事件完成时做出反应(例如我们的 AJAX 调用,或者在本例中是超时)。用法是:
应该在屏幕上提醒“5”。 (小提琴)。
可能的解决方案
基本上有两种方法可以解决此问题:
1. 同步 AJAX - 不要这样做!
至于同步 AJAX,不要这样做!Felix 的回答提出了一些令人信服的论据,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并造成非常糟糕的用户体验。以下是来自 MDN 的另一个简短总结,说明原因:
如果您必须这样做,您可以传递一个标志。 具体方法如下:
2. 重构代码
让你的函数接受回调。在示例代码中,可以使
foo
接受回调。我们将告诉代码当foo
完成时如何反应。所以:
变成:
这里我们传递了一个匿名函数,但我们可以轻松地传递对现有函数的引用,使其看起来像:
有关如何完成这种回调设计的更多详细信息,请查看 Felix 的答案。
现在,让我们定义 foo 本身以进行相应的操作
(fiddle)
我们现在已经制作了我们的 foo 函数接受 AJAX 成功完成时运行的操作。我们可以通过检查响应状态是否不是 200 并采取相应措施(创建失败处理程序等)来进一步扩展此功能。它有效地解决了我们的问题。
如果您仍然很难理解这一点,阅读 AJAX 入门指南< /a> 在 MDN。
If you're not using jQuery in your code, this answer is for you
Your code should be something along the lines of this:
Felix Kling did a fine job writing an answer for people using jQuery for AJAX, but I've decided to provide an alternative for people who aren't.
(Note, for those using the new
fetch
API, Angular or promises I've added another answer below)What you're facing
This is a short summary of "Explanation of the problem" from the other answer, if you're not sure after reading this, read that.
The A in AJAX stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example,
.send
returns immediately and the next statement,return result;
, is executed before the function you passed assuccess
callback was even called.This means when you're returning, the listener you've defined did not execute yet, which means the value you're returning has not been defined.
Here is a simple analogy:
(Fiddle)
The value of
a
returned isundefined
since thea=5
part has not executed yet. AJAX acts like this, you're returning the value before the server got the chance to tell your browser what that value is.One possible solution to this problem is to code re-actively , telling your program what to do when the calculation completed.
This is called CPS. Basically, we're passing
getFive
an action to perform when it completes, we're telling our code how to react when an event completes (like our AJAX call, or in this case the timeout).Usage would be:
Which should alert "5" to the screen. (Fiddle).
Possible solutions
There are basically two ways how to solve this:
1. Synchronous AJAX - Don't do it!!
As for synchronous AJAX, don't do it! Felix's answer raises some compelling arguments about why it's a bad idea. To sum it up, it'll freeze the user's browser until the server returns the response and create a very bad user experience. Here is another short summary taken from MDN on why:
If you have to do it, you can pass a flag. Here is how:
2. Restructure code
Let your function accept a callback. In the example code
foo
can be made to accept a callback. We'll be telling our code how to react whenfoo
completes.So:
Becomes:
Here we passed an anonymous function, but we could just as easily pass a reference to an existing function, making it look like:
For more details on how this sort of callback design is done, check Felix's answer.
Now, let's define foo itself to act accordingly
(fiddle)
We have now made our foo function accept an action to run when the AJAX completes successfully. We can extend this further by checking if the response status is not 200 and acting accordingly (create a fail handler and such). Effectively it is solving our issue.
If you're still having a hard time understanding this, read the AJAX getting started guide at MDN.
XMLHttpRequest 2(首先,阅读本杰明·格伦鲍姆和Felix Kling)
如果您不使用 jQuery 并且想要一个可以在现代浏览器和移动浏览器中工作的简短 XMLHttpRequest 2,我建议这样使用它:
如您所见:
有两种方法可以获得此 Ajax 调用的响应(三种使用 XMLHttpRequest 变量名称):
最简单的:
或者如果由于某种原因您将回调绑定到类:
示例:
或者 (上面的更好,匿名函数总是一个问题):
没有比这更容易的了。
现在有些人可能会说最好使用 onreadystatechange 甚至 XMLHttpRequest 变量名。那是错误的。
查看XMLHttpRequest 高级功能。
它支持所有*现代浏览器。我可以确认,自从 XMLHttpRequest 2 创建以来我一直在使用这种方法。我在使用的任何浏览器中从未遇到过任何类型的问题。
onreadystatechange 仅在您想获取状态 2 上的标头时有用。
使用
XMLHttpRequest
变量名是另一个大错误,因为您需要在 onload/oreadystatechange 闭包内执行回调,否则您会丢失它。现在,如果您想要使用 POST 和 FormData 进行更复杂的操作,您可以轻松扩展此函数:
再次...这是一个非常短的函数,但它确实 GET 和 POST。
使用示例:
或传递完整的表单元素 (
document.getElementsByTagName('form')[0]
):或设置一些自定义值:
如您所见,我没有实现同步..这是一件坏事。
话虽如此……为什么我们不采取简单的方法呢?
正如评论中提到的,使用 error &&同步确实完全打破了答案的要点。哪一个是正确使用 Ajax 的好简短方法?
错误处理程序
在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会影响功能。错误处理程序也可用于其他功能。
但要消除错误,唯一方法是编写错误的 URL,在这种情况下,每个浏览器都会抛出错误。
如果您设置自定义标头、将响应类型设置为 blob 数组缓冲区或其他任何内容,错误处理程序可能会很有用...
即使您传递“POSTAPAPAP”作为方法,它也不会抛出错误。
即使您将“fdggdgilfdghfldj”作为表单数据传递,它也不会抛出错误。
在第一种情况下,错误位于
this.statusText
下的displayAjax()
内,为Method not allowed
。在第二种情况下,它确实有效。您必须检查服务器端是否传递了正确的发布数据。
不允许跨域会自动抛出错误。
在错误响应中,没有任何错误代码。
只有
this.type
设置为 error。如果您无法控制错误,为什么还要添加错误处理程序?
大多数错误都在回调函数
displayAjax()
中返回。因此:如果您能够正确复制并粘贴 URL,则无需进行错误检查。 ;)
PS:作为第一个测试,我编写了 x('x', displayAjax)...,它完全得到了响应...???于是我检查了HTML所在的文件夹,发现有一个名为“x.xml”的文件。因此,即使您忘记了文件的扩展名,XMLHttpRequest 2 也会找到它。。我哈哈
同步读取文件
不要这样做。
如果你想阻止浏览器一段时间,请加载一个漂亮的大
.txt
文件同步。现在你可以做
没有其他方法可以以非异步方式做到这一点。 (是的,使用 setTimeout 循环...但是认真的吗?)
另一点是...如果您使用 API 或只是您自己的列表文件或任何您总是为每个请求使用不同函数的内容...
仅当您有一个页面时您始终加载相同的 XML/JSON 或任何您只需要一个函数的内容。在这种情况下,请稍微修改 Ajax 函数并将 b 替换为您的特殊函数。
以上功能仅供基本使用。
如果您想扩展该功能...
是的,您可以。
我使用了很多 API,我集成到每个 HTML 页面中的第一个函数是这个答案中的第一个 Ajax 函数,仅使用 GET...
但是您可以使用 XMLHttpRequest 2 做很多事情:
我做了一个下载管理器(使用简历、文件阅读器和文件系统两侧的范围)、使用画布的各种图像缩放转换器、使用 base64 图像填充 Web SQL 数据库等等......
但在这些情况下,您应该仅为此目的创建一个函数。 ..有时你需要一个 blob 或数组缓冲区,您可以设置标头、覆盖 mimetype 以及更多...
但这里的问题是如何返回 Ajax 响应...(我添加了一个简单的方法。)
XMLHttpRequest 2 (first of all, read the answers from Benjamin Gruenbaum and Felix Kling)
If you don't use jQuery and want a nice short XMLHttpRequest 2 which works in modern browsers and also in mobile browsers, I suggest using it this way:
As you can see:
There are two ways to get the response of this Ajax call (three using the XMLHttpRequest var name):
The simplest:
Or if for some reason you
bind()
the callback to a class:Example:
Or (the above one is better anonymous functions are always a problem):
Nothing easier.
Now some people will probably say that it's better to use onreadystatechange or even the XMLHttpRequest variable name. That's wrong.
Check out XMLHttpRequest advanced features.
It supported all *modern browsers. And I can confirm as I have been using this approach since XMLHttpRequest 2 was created. I never had any type of problem in any browsers I used.
onreadystatechange is only useful if you want to get the headers on state 2.
Using the
XMLHttpRequest
variable name is another big error as you need to execute the callback inside the onload/oreadystatechange closures, or else you lost it.Now if you want something more complex using POST and FormData you can easily extend this function:
Again ... it's a very short function, but it does GET and POST.
Examples of usage:
Or pass a full form element (
document.getElementsByTagName('form')[0]
):Or set some custom values:
As you can see, I didn't implement sync... it's a bad thing.
Having said that ... why don't we do it the easy way?
As mentioned in the comment, the use of error && synchronous does completely break the point of the answer. Which is a nice short way to use Ajax properly?
Error handler
In the above script, you have an error handler which is statically defined, so it does not compromise the function. The error handler can be used for other functions too.
But to get out an error, the only way is to write a wrong URL in which case every browser throws an error.
Error handlers may maybe useful if you set custom headers, set the responseType to blob array buffer, or whatever...
Even if you pass 'POSTAPAPAP' as the method it won't throw an error.
Even if you pass 'fdggdgilfdghfldj' as formdata it won't throw an error.
In the first case, the error is inside the
displayAjax()
underthis.statusText
asMethod not Allowed
.In the second case, it simply works. You have to check on the server side if you passed the right post data.
Cross-domain not allowed throws an error automatically.
In the error response, there aren't any error codes.
There is only the
this.type
which is set to error.Why add an error handler if you don't have any control over errors?
Most of the errors are returned inside this in the callback function
displayAjax()
.So: There isn't any need for error checks if you're able to copy and paste the URL properly. ;)
PS: As the first test I wrote x('x', displayAjax)..., and it totally got a response...??? So I checked the folder where the HTML is located, and there was a file called 'x.xml'. So even if you forget the extension of your file XMLHttpRequest 2 WILL FIND IT. I LOL'd
Read a file synchronous
Don't do that.
If you want to block the browser for a while load a nice big
.txt
file synchronous.Now you can do
There is no other way to do this in a non-asynchronous way. (Yeah, with setTimeout loop... but seriously?)
Another point is... if you work with APIs or just your own list's files or whatever you always use different functions for each request...
Only if you have a page where you load always the same XML/JSON or whatever you need only one function. In that case, modify a little the Ajax function and replace b with your special function.
The functions above are for basic use.
If you want to extend the function...
Yes, you can.
I'm using a lot of APIs and one of the first functions I integrate into every HTML page is the first Ajax function in this answer, with GET only...
But you can do a lot of stuff with XMLHttpRequest 2:
I made a download manager (using ranges on both sides with resume, filereader, and filesystem), various image resizers converters using canvas, populate web SQL databases with base64images and much more...
But in these cases you should create a function only for that purpose... sometimes you need a blob, or array buffers, you can set headers, override mimetype and there is a lot more...
But the question here is how to return an Ajax response... (I added an easy way.)
如果您使用承诺,那么这个答案适合您。
这意味着 AngularJS、jQuery(延迟)、原生 XHR 的替代品(获取)、Ember.js,Backbone.js 的保存或任何 Node.js 返回承诺的库。
您的代码应该类似于以下内容:
Felix Kling 做得很好,为使用带有 Ajax 回调的 jQuery 的人们编写了答案。我有一个关于原生 XHR 的答案。这个答案适用于前端或后端的承诺的通用用法。
核心问题
浏览器和 Node.js/io.js 服务器中的 JavaScript 并发模型是异步和反应式。
每当您调用返回 Promise 的方法时,
then
处理程序总是异步执行 - 也就是说,在它们下面的代码之后在.then
处理程序中。这意味着当您返回
data
时,您定义的then
处理程序尚未执行。这又意味着您返回的值尚未及时设置为正确的值。对于这个问题,有一个简单的类比:
由于
data = 5
部分尚未执行,因此data
的值为undefined
。它可能会在一秒钟内执行,但到那时它与返回值无关。由于操作尚未发生(Ajax、服务器调用、I/O 和计时器),您将在请求有机会告诉代码该值是什么之前返回该值。
解决此问题的一种可能的解决方案是重新主动编写代码,告诉您的程序计算完成后要做什么。 Promise 通过本质上是暂时的(时间敏感的)来积极实现这一点。
快速回顾一下 Promise
Promise 是随着时间的推移而产生的价值。承诺有状态。它们开始时处于未决状态,没有任何价值,并且可以达到:
Promise 只能改变状态一次,之后它将永远保持相同的状态。您可以将
then
处理程序附加到 Promise 以提取其值并处理错误。then
处理程序允许链接调用。 Promise 是通过使用返回它们的 API 创建的。例如,更现代的 Ajax 替代fetch
或 jQuery 的$.get
返回承诺。当我们对 Promise 调用
.then
并从中返回一些内容时,我们会得到一个关于处理后的值的 Promise。如果我们再次兑现承诺,我们会得到令人惊奇的东西,但我们还是先放马吧。使用 Promise
让我们看看如何使用 Promise 来解决上述问题。首先,让我们使用 Promise 构造函数 用于创建延迟函数:
现在,在我们 转换setTimeout来使用promise,我们可以使用 < code>then 使其计数:
基本上,我们不是返回一个由于并发模型而无法执行的值,而是返回一个可以解包<的值的包装器 /em> 与
then
。它就像一个可以用then
打开的盒子。应用这个
这对于你原来的 API 调用来说是一样的,你可以:
所以这也同样有效。我们已经了解到,我们无法从异步调用中返回值,但我们可以使用 Promise 并将它们链接起来来执行处理。我们现在知道如何从异步调用返回响应。
ES2015 (ES6)
ES6 引入了生成器,是可以在中间返回然后恢复到原来位置的函数。这通常对序列有用,例如:
是一个在序列
1,2,3,3,3,3,....
上返回迭代器的函数可以迭代。虽然这本身很有趣并且为很多可能性提供了空间,但有一个特别有趣的案例。如果我们生成的序列是一系列操作而不是数字 - 我们可以在生成操作时暂停该函数,并在恢复该函数之前等待它。因此,我们需要的不是一系列数字,而是一系列未来值 - 即:承诺。
这是一个有点棘手但非常强大的技巧,让我们以同步方式编写异步代码。有几个“跑步者”可以为您执行此操作。编写一个代码只需短短几行代码,但这超出了本答案的范围。我将在这里使用 Bluebird 的
Promise.coroutine
,但还有其他包装器,例如co
或Q.async
。该方法本身返回一个承诺,我们可以从其他协程中使用它。例如:
ES2016(ES7)
在ES7中,这进一步标准化。目前有几个提案,但在所有提案中您都可以
await
承诺。这只是上述 ES6 提案的“糖”(更好的语法),添加了async
和await
关键字。以上面的例子为例:它仍然返回一个相同的承诺:)
If you're using promises, this answer is for you.
This means AngularJS, jQuery (with deferred), native XHR's replacement (fetch), Ember.js, Backbone.js's save or any Node.js library that returns promises.
Your code should be something along the lines of this:
Felix Kling did a fine job writing an answer for people using jQuery with callbacks for Ajax. I have an answer for native XHR. This answer is for generic usage of promises either on the frontend or backend.
The core issue
The JavaScript concurrency model in the browser and on the server with Node.js/io.js is asynchronous and reactive.
Whenever you call a method that returns a promise, the
then
handlers are always executed asynchronously - that is, after the code below them that is not in a.then
handler.This means when you're returning
data
thethen
handler you've defined did not execute yet. This in turn means that the value you're returning has not been set to the correct value in time.Here is a simple analogy for the issue:
The value of
data
isundefined
since thedata = 5
part has not executed yet. It will likely execute in a second, but by that time it is irrelevant to the returned value.Since the operation did not happen yet (Ajax, server call, I/O, and timer) you're returning the value before the request got the chance to tell your code what that value is.
One possible solution to this problem is to code re-actively, telling your program what to do when the calculation completed. Promises actively enable this by being temporal (time-sensitive) in nature.
Quick recap on promises
A Promise is a value over time. Promises have state. They start as pending with no value and can settle to:
A promise can only change states once after which it will always stay at the same state forever. You can attach
then
handlers to promises to extract their value and handle errors.then
handlers allow chaining of calls. Promises are created by using APIs that return them. For example, the more modern Ajax replacementfetch
or jQuery's$.get
return promises.When we call
.then
on a promise and return something from it - we get a promise for the processed value. If we return another promise we'll get amazing things, but let's hold our horses.With promises
Let's see how we can solve the above issue with promises. First, let's demonstrate our understanding of promise states from above by using the Promise constructor for creating a delay function:
Now, after we converted setTimeout to use promises, we can use
then
to make it count:Basically, instead of returning a value which we can't do because of the concurrency model - we're returning a wrapper for a value that we can unwrap with
then
. It's like a box you can open withthen
.Applying this
This stands the same for your original API call, you can:
So this works just as well. We've learned we can't return values from already asynchronous calls, but we can use promises and chain them to perform processing. We now know how to return the response from an asynchronous call.
ES2015 (ES6)
ES6 introduces generators which are functions that can return in the middle and then resume the point they were at. This is typically useful for sequences, for example:
Is a function that returns an iterator over the sequence
1,2,3,3,3,3,....
which can be iterated. While this is interesting on its own and opens room for a lot of possibility, there is one particular interesting case.If the sequence we're producing is a sequence of actions rather than numbers - we can pause the function whenever an action is yielded and wait for it before we resume the function. So instead of a sequence of numbers, we need a sequence of future values - that is: promises.
This somewhat a tricky, but very powerful trick let’s us write asynchronous code in a synchronous manner. There are several "runners" that do this for you. Writing one is a short few lines of code, but it is beyond the scope of this answer. I'll be using Bluebird's
Promise.coroutine
here, but there are other wrappers likeco
orQ.async
.This method returns a promise itself, which we can consume from other coroutines. For example:
ES2016 (ES7)
In ES7, this is further standardized. There are several proposals right now, but in all of them you can
await
promise. This is just "sugar" (nicer syntax) for the ES6 proposal above by adding theasync
andawait
keywords. Making the above example:It still returns a promise just the same :)
您错误地使用了 Ajax。我们的想法是不让它返回任何内容,而是将数据交给回调函数来处理数据。
也就是说:
在提交处理程序中返回任何内容都不会执行任何操作。您必须要么移交数据,要么直接在 success 函数内对其执行您想要的操作。
You are using Ajax incorrectly. The idea is not to have it return anything, but instead hand off the data to something called a callback function, which handles the data.
That is:
Returning anything in the submit handler will not do anything. You must instead either hand off the data, or do what you want with it directly inside the success function.