How To Return The Response From An Asynchronous Call
Solution 1:
→ For a more general explanation of asynchronous behaviour with different examples, see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
→ If you already understand the problem, skip to the possible solutions below.
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 as success
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:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Even though findItem
might take a long time to execute, any code coming after var 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.
findItem(function(item) {
// Do something with the item
});
doSomethingElse();
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:
- Promises with
async/await
(ES2017+, available in older browsers if you use a transpiler or regenerator) - Callbacks (popular in node)
- Promises with
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
and await
, 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: an async
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 an async
function. Right now, top-level await
isn't yet supported, so you might have to make an async IIFE (Immediately Invoked Function Expression) to start an async
context.
You can read more about async
and await
on MDN.
Here is an example that elaborates the delay function findItem()
above:
// 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);
})();
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 as success
callback. So this
var result = foo();
// Code that depends on 'result'
becomes
foo(function(result) {
// Code that depends on 'result'
});
Here we defined the function "inline" but you can pass any function reference:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
itself is defined as follows:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
will refer to the function we pass to foo
when we call it and we pass it on to success
. I.e. once the Ajax request is successful, $.ajax
will call callback
and pass the response to the callback (which can be referred to with result
, since this is how we defined the callback).
You can also process the response before passing it to the callback:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
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:
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).
});
.as-console-wrapper { max-height: 100% !important; top: 0; }
Applied to our Ajax call we could use promises like this:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("https://jsonplaceholder.typicode.com/todos/1")
.then(function(result) {
console.log(result); // Code depending on result
})
.catch(function() {
// An error occurred
});
.as-console-wrapper { max-height: 100% !important; top: 0; }
Describing all the advantages that promise offer is beyond the scope of this answer, but if you write new code, you should seriously consider them. They provide a great abstraction and separation of your code.
More information about promises: HTML5 rocks - JavaScript Promises.
Side note: jQuery's deferred objects
Deferred objects are jQuery's custom implementation of promises (before the Promise API was standardized). They behave almost like promises but expose a slightly different API.
Every Ajax method of jQuery already returns a "deferred object" (actually a promise of a deferred object) which you can just return from your function:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
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:
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
}
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 the if
statement is going to always get this Deferred object, treat it as true
, and proceed as though the user is logged in. Not good.
But the fix is easy:
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
});
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, pass false
as third argument to .open
.
jQuery
If you use jQuery, you can set the async
option to false
. Note that this option is deprecated since jQuery 1.8.
You can then either still use a success
callback or access the responseText
property of the jqXHR object:
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
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).
Solution 2:
If you're not using jQuery in your code, this answer is for you
Your code should be something along the lines of this:
function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // Always ends up being 'undefined'
Felix Kling 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 as success
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:
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
The value of a
returned is undefined
since the a=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.
function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
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:
getFive(onComplete);
Which should alert "5" to the screen. (Fiddle).
Possible solutions
There are basically two ways how to solve this:
- Make the AJAX call synchronous (let’s call it SJAX).
- Restructure your code to work properly with callbacks.
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:
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...
If you have to do it, you can pass a flag. Here is how:
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. 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 when foo
completes.
So:
var result = foo();
// Code that depends on `result` goes here
Becomes:
foo(function(result) {
// Code that depends on `result`
});
Here we passed an anonymous function, but we could just as easily pass a reference to an existing function, making it look like:
function myHandler(result) {
// Code that depends on `result`
}
foo(myHandler);
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
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();
}
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.
Solution 3:
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 the modern browsers and also in the mobile browsers, I suggest to use it this way:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
As you can see:
- It's shorter than all other functions Listed.
- The callback is set directly (so no extra unnecessary closures).
- It uses the new onload (so you don't have to check for readystate && status)
- There are some other situations, which I don't remember, that make the XMLHttpRequest 1 annoying.
There are two ways to get the response of this Ajax call (three using the XMLHttpRequest var name):
The simplest:
this.response
Or if for some reason you bind()
the callback to a class:
e.target.response
Example:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Or (the above one is better anonymous functions are always a problem):
ajax('URL', function(e){console.log(this.response)});
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 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:
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)
}
Again ... it's a very short function, but it does GET and POST.
Examples of usage:
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
Or pass a full form element (document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
Or set some custom values:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
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 in the proper way?
Error handler
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);
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()
under this.statusText
as Method 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 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 totally 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.
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Now you can do
var res = omg('thisIsGonnaBlockThePage.txt');
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, 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.)
Solution 4:
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:
function foo() {
var data;
// Or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // 'result' is always undefined no matter what.
Felix Kling 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
the then
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:
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
The value of data
is undefined
since the data = 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:
- fulfilled meaning that the computation completed successfully.
- rejected meaning that the computation failed.
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 replacement fetch
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:
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);
});
}
Now, after we converted setTimeout to use promises, we can use then
to make it count:
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, and we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
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 with then
.
Applying this
This stands the same for your original API call, you can:
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`
})
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:
function* foo(){ // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js only
yield 1;
yield 2;
while(true) yield 3;
}
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 like co
or Q.async
.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // Notice the yield
// The code here only executes _after_ the request is done
return data.json(); // 'data' is defined
});
This method returns a promise itself, which we can consume from other coroutines. For example:
var main = coroutine(function*(){
var bar = yield foo(); // Wait our earlier coroutine. It returns a promise
// The server call is done here, and the code below executes when done
var baz = yield fetch("/api/users/" + bar.userid); // Depends on foo's result
console.log(baz); // Runs after both requests are done
});
main();
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 the async
and await
keywords. Making the above example:
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
}
It still returns a promise just the same :)
Solution 5:
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:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
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.
Post a Comment for "How To Return The Response From An Asynchronous Call"