Writing code with huge nested structures in JavaScript is not a good idea anymore, as it can lead to the "callback hell". Callback hell is caused by poor coding practices. To avoid callback hell you should break your code up into small and reusable modules instead of giant monolithic blobs that will make your life unendurable and miserable.

var xhttp = new XMLHttpRequest();

xhttp.onreadystatechange = function() {
    if (xhttp.readyState === 4) {  
        if (xhttp.status === 200) {  
            /* Handle response */
        } else {  
            /* Handle errors */
        }  
    }  
};

xhttp.open("GET", url, true);
xhttp.send(); 

Using fetch and Promises

You can use the fetch function, which makes it easy to work with asynchronous AJAX data retrieval, but then it becomes possible that you will have to replace callbacks hell with Promises hell.

const options = {};

fetch(url, options)
    .then(response => response.json())
    .then(body => { /* Handle response */ })
    .catch(error => { /* Handle errors */ });

Using asynchronous fetch

You can also use asynchronous functions, that are a proposed ES7 feature that will further wrap generators and promises in a higher level syntax.

Note that these fancy things from ES7 may introduce performance and/or cross platform runtime compatibility issues, so make sure to do your research.

Before we rewrite our code to asynchronous functions, let's learn how to deal with async/await construction. Defining a function, the keyword async should be placed before the function keyword.

const requestData = async () => {
    // async code goes here
};

Keyword await should be placed in front of a promise call to pauses the execution of an async function and waits for the promise to resolve. Keep in mind, that it only works when used in an async function and only when placed in front of a Promise. Our code example will now look like as follows:

const requestData = async () => {
    try {
        const options = {};
        const response = await fetch(url, options);

        const json = await response.json();

        if (response.status === 200 && json.success === true) {
            /* Handle response */
        } else {
            /* Handle responce errors */
        }
    } catch (error) {
        /* Handle fetch errors */
    }
};

The other way to handle errors with async functions is by chaining a catch on to the function call.

const requestData = async () => {
    const response = await fetch(url);

    const json = await response.json();

    /* Handle response */
};

requestData().catch(error => { /* Handle errors */ });

Requesting multiple sources

In case you need request multiple resources, we are faced with a problem that we lose the advantages of asynchronous code execution. In order to reasonable use await construction, we will apply it to the resolved promises with Promise.all(), not to the result of fetch() functions.

const requestData = async () => {
    // Fire both requests at the same time
    const firstPromise = axion(firstUrl);
    const secondPromise = axion(secondUrl);

    // Wait for both requests to be resolved
    const [firstResponse, secondResponse] = await Promise.all([ firstPromise, secondPromise ]);

    /* Handle responses */
};

In example above we used helper library axion, which parses the body of the response for us. Now we can write code which looks synchronous. As you can see, async/await could be very handy feature in your code, that helps to make it more readable and easier to understand.


ResourcesClick to expand/shrink

  1. An Introduction to async/await