Skip to main content

Return the response from an asynchronous call


Embrace the asynchronous behavior of JavaScript! While some asynchronous actions offer synchronous equivalents (so does "Ajax"), it's usually discouraged to use them, particularly in a browser context.

Why is it not good do you ask?

JavaScript goes in the UI thread of the browser and any long running process will lock the UI, creating it unresponsive. Moreover, there is an upper limit on the performance time for JavaScript and the browser will ask the user whether to continue the implementation or not.

All of this is really not good user experience. The user won't be able to tell whether all is working fine or not. Also, the effect will be not good for users with a slow connection.

In the following we will look at three different answers that are all building on top of each other:

Promises with async/await (ES2017+, offered in older browsers if you use a transpiler or regenerator)
Callbacks (popular in node)
Promises with then() (ES2015+, offered in older browsers if you use one of the many promise libraries)
All  the three are offered in present browsers, and node 7+.

ES2017+: Promises with async/await
The ECMAScript version released in 2017 announced syntax-level support for asynchronous functions. With the advantage 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 forms on top of promises: an async function always returns a promise. await "unpacks" a promise and either result in the value the promise was fixed with or throws an error if the promise was forbidden.

Key: You can only use await inside an async function. At present, top-level await isn't yet supported, so you may have to make an async IIFE (Immediately Invoked Function Expression) to start an async context.

You can see more about async and await on MDN.

Here is an illustration that forms on top of delay above:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't stated 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 solve or reject the promise
    setTimeout(function() {
      resolve(42); // After three seconds, solve 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 anticipated promises was rejected, this catch block
    // would catch the rejection cause
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();
Existing browser and node versions support async/await. You can also support older environments by altering your code to ES5 with the assistance of regenerator (or tools that use regenerator, such as Babel).

Let functions accept callbacks
A callback is basically a function passed to a different function. That other function can call the function passed at whatever time it is ready. In the situation of an asynchronous process, the callback will be called at whatever time the asynchronous process is completed. Typically, the result is passed to the callback.

In the illustration 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 state the function "inline" but you can pass any function reference:

function myCallback(result) {
    // Code that be subject to 'result'
}

foo(myCallback);
foo itself is defined as follows:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}
callback will denote to the function we pass to foo when we call it and we basically pass it on to success. I.e. once the Ajax request is effective, $.ajax will call callback and pass the response to the callback (which can be stated with the outcome, meanwhile this is how we defined the callback).

You can also process the response beforehand 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 might appear. After all, JavaScript in the browser is deeply event-driven (DOM events). Getting the Ajax response is nothing else but an event.
Complications might arise when you have to work with third-party code, but most difficulties can be resolved by just thinking through the application flow.

ES2015+: Promises with then()
The Promise API is a fresh feature of ECMAScript 6 (ES2015), but it has decent browser support now. There are also numerous libraries which implement the normal Promises API and provide added methods to simplify the use and composition of asynchronous functions (e.g. bluebird).

Promises are containers for forthcoming values. When the promise gets the value (it is resolved) or when it is void (rejected), it informs all of its "listeners" who want to access this value.

The benefit over basic callbacks is that they let you to decouple your code and they are easier to compose.

Here is a simple illustration of using a promise:

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

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is solved
  })
  .catch(function(v) {
    // Or do something else if it is rejected
    // (it would not occur in this illustration, as `reject` is not called).
  });
Given 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("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
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 deliver a great abstraction and separation of your code.

More info about promises: HTML5 rocks - JavaScript Promises

Side note: jQuery's deferred objects
Deferred objects are jQuery's norm application of promises (before the Promise API was uniform). They act nearly like promises but picture a somewhat dissimilar API.

Every Ajax method of jQuery now returns a "deferred object" (essentially 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 notice that promises and deferred objects are just containers for upcoming values, they are not the value itself. For instance, assume 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 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 continuously get this Deferred object, treat it as true, and continue as nevertheless 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 stated, some (!) asynchronous processes have synchronous equivalents.
I don't promote their use, but for completeness' sake, here is how you would do a synchronous call:

Without jQuery
If you straightly use a XMLHTTPRequest object, pass false as 3rd argument to .open.

jQuery
If you use jQuery, you can set the value of async option to false. Note that this option is deprecated as jQuery 1.8.
You can then also still use a success callback or access the responseText stuff of the jqXHR object:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}
If you use any additional jQuery Ajax method, such as $.get, $.getJSON, etc., you have to alter it to $.ajax (as you can only pass configuration parameters to $.ajax).

Comments