Search code examples
javascriptasynchronousbrowserxmlhttprequest

async XMLHttpRequest blocking UI in Browser


I have the following function in the client-side of a web app:

function fetchDataFromApi(fetchCode, options, callback) {

    var dataObject = JSON;
    dataObject.fetchCode = fetchCode;
    dataObject.options = options;

    var xhr = new XMLHttpRequest();
    var url = "DATA_API_URL"; 

    // connect to the API
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-Type",
        "application/json"
    ); 

    // set callback for when API responds. This will be called once the request is answered by the API.
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            // API has responded; 
            var json = {
                ok: false,
                message: 'could not parse response'
            };
            try {
                // parse the raw response into the API response object
                json = JSON.parse(xhr.responseText);
            } catch (err) {
                // probably json parse error; show raw response and error message
                console.log(err);
                console.log("raw response: " + xhr.responseText);
            } 

            if (json.ok) {
                // success, execute callback with argument json.data
                callback(json.data);
            } else {
                // fetch failed; 
                console.error(json.message);
            }
        }
    }; 

    // send request payload to API
    var data = JSON.stringify(dataObject);
    xhr.send(data);
}

Since I am using an asynchronous call (the third parameter in xhr.open is set to true), I am surprised to find that this function blocks the UI in the browser. When there is a substantial amount of data grabbed from the server with this function, it can take 3-4 seconds, blocking the UI and generating this error in the Chrome console:

[Violation] 'load' handler took 3340ms

This function is currently in production here, where I am calling the function as so:

function getNamesFromApi() {
    fetchDataFromApi('chj-confraternity-list', {}, function (data) {
        fadeReplace(document.getElementById('spinner-2'), document.getElementById(
                'name-list-container'),
            false, true);
        // transaction was successful; display names
        var listString = "";
        if (data.list) {
            // add the names to the page
            var listLength = data.list.length;
            for (var x = 0; x < listLength; x++) {
                document.getElementById('name-list-container').innerHTML +=
                    "<div class='name-list-item'>" +
                    "<span class='name-list-name'>" +
                    data.list[x].name +
                    "</span>" +
                    "<span class='name-list-location'>" +
                        data.list[x].location +
                    "</span>" +
                    "</div>";
            }
        }
    });
}

window.addEventListener('load', function() {
    getNamesFromApi();
});

Why is this blocking the UI, and what am I doing wrong in making an asynchronous XMLHttpRequest?

UPDATE: Thanks to the comments for pointing me in the right direction; the issue was not the XMLHttpRequest, but rather me appending innerHTMl within a loop. The issue is now fixed, with the corrected snippet in the answer.


Solution

  • The UI was blocked because i was appending innerHTML within a loop, an expensive, and UI-blocking operation. The issue is now fixed. Here is the corrected snippet:

    function getNamesFromApi() {
        fetchDataFromApi('chj-confraternity-list', {}, function (data) {
            fadeReplace(document.getElementById('spinner-2'), document.getElementById(
                    'name-list-container'),
                false, true);
            // transaction was successful; display names
            if (data.list) {
                var listString = "";
                // add the names to the page
                var listLength = data.list.length;
                for (var x = 0; x < listLength; x++) {
                    listString +=
                        "<div class='name-list-item'>" +
                        "<span class='name-list-name'>" +
                        data.list[x].name +
                        "</span>" +
                        "<span class='name-list-location'>" +
                            data.list[x].location +
                        "</span>" +
                        "</div>";
                }
                document.getElementById('name-list-container').innerHTML = listString;
            }
        });
    }
    
    window.addEventListener('load', function() {
        getNamesFromApi();
    });