I have a function foo
which makes an Ajax request. How can I return the response from foo
?
I tried returning the value from the success
callback as well as assigning the response to a local variable inside the function and returning that one, but none of those ways actually return the response.
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`.
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)
Here are some approaches to work with asynchronous requests:
Example: jQuery deferred implementation to work with multiple requests
If you're using promises, this answer is for you.
This means AngularJS, jQuery (with deferred), native XHR's replacement (fetch), EmberJS, BackboneJS's save or any node 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 NodeJS/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, IO, 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 tricky but very powerful trick lets 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 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 :)
You can use this custom library (written using Promise) to make a remote call.
Simple usage example:
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, 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 solving our issue.
If you're still having a hard time understanding this read the AJAX getting started guide at MDN.
XMLHttpRequest 2 (first of all read the answers from Benjamin Gruenbaum & Felix Kling)
If you don't use jQuery and want a nice short XMLHttpRequest 2 which works on the modern browsers and also on the mobile browsers I suggest to use 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 the even the XMLHttpRequest variable name. That's wrong.
Check out XMLHttpRequest advanced features
It supported all *modern browsers. And I can confirm as I'm using this approach since XMLHttpRequest 2 exists. I never had any type of problem on all browsers I use.
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 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 & 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 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 in the proper way?
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 really get out an error the only way is to write a wrong URL in which case every browser throws an error.
Error handlers are 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 at the server side if you passed the right post data.
cross-domain not allowed throws error automatically.
In the error response, there are no error codes.
There is only the
this.type
which is set to error.Why add an error handler if you totally have no control over errors? Most of the errors are returned inside this in the callback function
displayAjax()
.So: No 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, 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, 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.)