How to return the response from an asynchronous call?

Answered Featured

0

0

I have a function foo which makes an Ajax request. How can I return the response from foo?

I tried to return the value from the success callback as well as assigning the response to a local variable inside the function and return 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`.

-> 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 successcallback 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 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 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 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.

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 is 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)

The new 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”. Make no mistake though: 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 results 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. That means that at the very top level, you still have to work directly with the promise.

// Using 'superagent' which will return a promise.var superagent = require('superagent')// This is isn't declared as `async` because it already returns a promisefunction delay(){// `delay` returns a promisereturnnewPromise(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);});}asyncfunction getAllBooks(){try{// GET a list of book IDs of the current uservar bookIDs =await superagent.get('/user/books');// wait for a second (just for the sake of this example)await delay(1000);// GET information about each bookreturnawait superagent.get('/books/ids='+JSON.stringify(bookIDs));}catch(error){// If any of the awaited promises was rejected, this catch block// would catch the rejection reasonreturnnull;}}// Async functions always return a promise
getAllBooks().then(function(books){
console.log(books);});

Newer 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 simply a function passed to another function. That other function can call the function passed whenever it is ready. In the context of asynchronous process, the callback will be called whenever the asynchronous process is done. Usually the result is passed to the callback.

In the example in the question, you can make foo accept a callback and use it as successcallback. 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:

callback will refer to the function we pass to foo when we call it and we simply 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).

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.

The Promise API is a new feature of ECMAScript 6 (ES2015), but it has good browser supportalready. 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 cancelled (rejected), it notifies all of its “listeners” who want to access this value.

The advantage over plain callbacks is that they allow you do decouple your code and they are easier to compose.

Here is a simple example of using a promise:

function delay(){// `delay` returns a promisereturnnewPromise(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).});

Describing all the advantages that promises 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.

This code misunderstands the above asynchrony 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, 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.

Without jQuery

jQuery

If you use jQuery, you can set the async option to false. Note that this option is deprecatedsince jQuery 1.8. You can then either still use a success callback or access the responseTextproperty of the jqXHR object: