javascript – How do I return the answer of an asynchronous call?

→ For a more general explanation of asynchronous behavior with different examples, see Why is my variable not modified after modifying it within a function? – Asynchronous code reference.

→ If you already understand the problem, go to the possible solutions below.

the A in Ajax means asynchronous . That means that the sending of the request (or, rather, the receipt of the response) is removed from the normal execution flow. In your example, $ .ajax Go back immediately and the following statement, return result, it is executed before the function that you passed as success it was even called callback.

Here is an analogy that, with luck, makes the difference between synchronous and asynchronous flow clearer:

Synchronous

Imagine that you make a phone call to a friend and ask him to find you something. Although it may take a while, you wait on the phone and look into space, until your friend gives you the answer you needed.

The same happens when you make a function call that contains the "normal" code:

findItem () {function
var item;
while (item_not_found) {
// search for
}
Return object;
}

var item = findItem ();

// Do something with the object
do something else ();

Even though find him it may take a long time to run, any code that comes after var item = findItem (); have to I waited Until the function returns the result.

Asynchronous

You call your friend again for the same reason. But this time you tell him you're in a hurry and he should call him back on your mobile phone. Hang up, get out of the house and do what you plan to do. Once your friend returns the call, you are dealing with the information he gave you.

That is exactly what is happening when you make an Ajax request.

findItem (function (item) {
// Do something with the object
});
do something else ();

Instead of waiting for the response, the execution continues immediately and the instruction is executed after the Ajax call. To get the answer eventually, provide a function to be called once the response is received, a call back (note something? call back ?) Any statement that comes after that call is executed before the callback is called.


Embrace the asynchronous nature of JavaScript! While certain asynchronous operations provide synchronous counterparts (so does "Ajax"), in general they are not recommended to be used, especially in the context of a browser.

Why is it bad you ask?

JavaScript runs on the sub-process of the user interface of the browser and any long-running process will block the user interface, which will stop responding. In addition, 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 this is really a bad user experience. The user can not tell if everything is working well or not. Also, the effect will be worse for users with a slow connection.

Next we will see three different solutions that are built on top of each other:

  • Promises with asynchronous / wait (ES2017 +, available in older browsers if you use a transpiler or a regenerator)
  • Callbacks (popular in the node)
  • Promises with so() (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 asynchronous / wait

The ECMAScript version launched in 2017 introduced syntax level support For asynchronous functions. With the help of asynchronous Y wait, you can write asynchronously in a "synchronous style". The code is still asynchronous, but it is easier to read / understand.

asynchronous / wait build on the promises: a asynchronous The function always returns a promise. wait "undo" a promise and result in the value with which the promise was resolved or throw an error if the promise was rejected.

Important: You can only use wait within a asynchronous function. At this time, high level. wait is not yet supported, so you may have to do an IIFE async (Feature Expression invoked immediately) to start a asynchronous context.

You can read more about asynchronous Y wait in MDN.

Here is an example that is based on the previous delay:

// Use & # 39; superagent & # 39; that will return a promise.
var superagent = require (& # 39; superagent & # 39;)

// This is not declared as `async` because it already returns a promise
delay function () {
// `delay` returns a promise
return new promise (function (resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout (function () {
solve (42); // After 3 seconds, solve the promise with value 42
}, 3000);
});
}


asynchronous function getAllBooks () {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get (& # 39; / user / books & # 39;);
// wait 3 seconds (only for this example)
wait delay ();
// GET information about each book
return awaits superagent.get (& # 39; / books / ids = & # 39; + JSON.stringify (bookIDs));
} catch (error) {
// If any of the expected promises was rejected, this capture block
// catch the reason for rejection
zero return
}
}

// Start an IIFE to use `await` at the top level
(asynchronous function () {
leave books = wait getAllBooks ();
console.log (books);
}) ();

Support for current browser and node versions asynchronous / wait. It can also support older environments by transforming its code to ES5 with the help of a regenerator (or tools that use a regenerator, such as Babel).


Let the functions accept callbacks

A callback is simply a function passed to another function. That other function can call the last function whenever it is ready. In the context of an asynchronous process, the callback will be called whenever the asynchronous process is performed. Usually, the result is passed to the callback.

In the example of the question, you can do foo accept a callback and use it as success call back. So this

result var = foo ();
// Code that depends on the & # 39; result & # 39;

becomes

foo (function (result) {
// Code that depends on the & # 39; result & # 39;
});

Here we define the function "in line" but can pass any function reference:

myCallback function (result) {
// Code that depends on the & # 39; result & # 39;
}

foo (myCallback);

foo In itself, it is defined as follows:

foo function (callback) {
$ .ajax ({
// ...
success: callback
});
}

call back will refer to the function that we pass on to foo when we call it and we simply pass it to success. That is to say. Once the Ajax application is successful, $ .ajax call to call back and pass the answer to the callback (which can be consulted with result, since this is how we define the callback).

You can also process the response before passing it to the callback:

foo function (callback) {
$ .ajax ({
// ...
success: function (answer) {
// For example, filter the answer
callback (filter_response);
}
});
}

It is easier to write code using callbacks than it seems. After all, JavaScript in the browser is heavily controlled by events (DOM events). Receiving the Ajax response is nothing more than an event.
Difficulties may arise when you have to work with a third-party code, but most problems can be solved simply by thinking about the flow of the application.


ES2015 +: Promises with then ()

The Promise API is a new feature of ECMAScript 6 (ES2015), but it already has good browser support. There are also many libraries that implement the standard Promises API and provide additional methods to facilitate the use and composition of asynchronous functions (for example, bluebird).

The promises are containers for future values. When the promise receives the value (it is resolved) or when canceled (rejected), notifies all of its "listeners" who wish to access this value.

The advantage over simple callbacks is that they allow you to decouple your code and are easier to write.

Here is a simple example of using a promise:

delay function () {
// `delay` returns a promise
return new promise (function (resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout (function () {
solve (42); // After 3 seconds, solve the promise with value 42
}, 3000);
});
}

to delay()
.then (function (v) {// `delay` returns a promise
console.log (v); // Register the value once resolved
})
.catch (function (v) {
// Or do something else if it is rejected.
// (it would not happen in this example, since it is not called "reject").
});

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 (& # 39; GET & # 39 ;, url);
xhr.send ();
});
}

ajax ("/ echo / json")
.then (function (result) {
// Code according to result.
})
.catch (function () {
// An error occurred
});

Describing all the advantages offered by the promise goes beyond the scope of this response, but if you write a new code, you should consider them seriously. They provide great abstraction and separation from your code.

More information about promises: HTML5 rocks – JavaScript Promises

Side note: deferred jQuery objects.

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

Each jQuery Ajax method already returns a "deferred object" (actually a promise of a deferred object) that it can return from its function:

ajax function () {
returns $ .ajax (...);
}

ajax (). done (function (result) {
// Code according to result.
}). fail (function () {
// An error occurred
});

Side note: Promise gotchas

Keep in mind that promises and deferred items are fair containers For a future value, they are not the value in itself. For example, suppose you have the following:

checkPassword () function {
returns $ .ajax ({
url: & # 39; / password & # 39 ;,
data: {
username: $ (& # 39; # username & # 39;). val (),
password: $ (& # 39; # password & # 39;). val ()
}
type: & # 39; POST & # 39 ;,
Data type: & # 39; json & # 39;
});
}

if (checkPassword ()) {
// Tell the user that they are connected
}

This code misinterprets previous problems of asynchrony. Specifically, $ .ajax () does not freeze the code while checking the page & # 39; / password & # 39; on your server: send a request to the server and, while waiting, immediately return a jQuery Ajax Deferred object, not the server's response. That means Yes The statement will always get this deferred object, treat it as true, and proceed as if the user had logged in. Not good.

But the solution is easy:

checkPassword ()
.done (function (r) {
yes (r) {
// Tell the user that they are connected
} else {
// Tell the user that his password was bad
}
})
.fail (function (x) {
// Tell the user that something bad happened
});

Not recommended: Synchronous calls "Ajax".

As I mentioned, some (!) Asynchronous operations have synchronous counterparts. I do not defend its use, but for the sake of integrity, here is how to make a synchronous call:

Without jQuery

If you use directly a XMLHTTP request object to pass false as a third argument for .open.

jQuery

If you use jQuery, you can configure the asynchronous option to false. Keep in mind that this option is obsolete from jQuery 1.8.
Then, you can use a success callback or access to the responseText property of the jqXHR object:

function foo () {
var jqXHR = $ .ajax ({
// ...
asynchronous: false
});
return jqXHR.responseText;
}

If you use any other jQuery Ajax method, like $ .get, $ .getJSON, etc., you have to change it to $ .ajax (since you can only pass configuration parameters to $ .ajax).

Notice! It is not possible to make a synchronous JSONP request. JSONP by its very nature is always asynchronous (one more reason not to consider this option).