In JavaScript how do I/should I use async/await with XMLHttpRequest?

JavascriptAsync AwaitXmlhttprequest

Javascript Problem Overview


Full disclosure: I'd qualify myself as having intermediate JavaScript knowledge. So this is slightly above my experience level at this time.

I've got a Google Chrome Extension that does an AJAX request for a local file:/// as soon as a page loads. After I get the response back from the request I use the returned code in several functions later on in my code. Most of the time I get the response back before my code that needs it runs. But sometimes I don't and everything breaks.

Now, I assume I could just throw all of the relevant code inside of the xhr.onload below. But that seems really inefficient? I have a lot of moving parts that rely on the response and it seems bad to put them all in there.

I've perused several articles related to async/await and I'm having trouble grasping the concept. I'm also not 100% positive I'm looking at this the right way. Should I even be considering using async/await?

Here is the code for my AJAX request.

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.onload = function(e) {
    code = xhr.response;
  };
  xhr.onerror = function () {
    console.error("** An error occurred during the XMLHttpRequest");
  };
  xhr.send();

Let's say I've got a bunch of functions that need to fire afterwards later on in my code. Right now they just look like:

function doTheThing(code) {
  // I hope the response is ready.
}

What's the best way to approach this? FYI, the Fetch API isn't an option.

Here's a high level view of how my code is structured.

// AJAX request begins.

// ...

// A whole bunch of synchronous code that isn't dependant on 
// the results of my AJAX request. (eg. Creating and appending
// some new DOM nodes, calculating some variables) I don't want
// to wait for the AJAX response when I could be building this stuff instead.

// ...

// Some synchronous code that is dependant on both my AJAX 
// request and the previous synchronous code being complete.

// ...

// Some more synchronous code that needs the above line to 
// be complete.

Javascript Solutions


Solution 1 - Javascript

I usually do async/await like this:

async function doAjaxThings() {
    // await code here
    let result = await makeRequest("GET", url);
    // code below here will only execute when await makeRequest() finished loading
    console.log(result);
}
document.addEventListener("DOMContentLoaded", function () {
    doAjaxThings();
    // create and manipulate your DOM here. doAjaxThings() will run asynchronously and not block your DOM rendering
    document.createElement("...");
    document.getElementById("...").addEventListener(...);
});

Promisified xhr function here:

function makeRequest(method, url) {
	return new Promise(function (resolve, reject) {
		let xhr = new XMLHttpRequest();
		xhr.open(method, url);
		xhr.onload = function () {
			if (this.status >= 200 && this.status < 300) {
				resolve(xhr.response);
			} else {
				reject({
					status: this.status,
					statusText: xhr.statusText
				});
			}
		};
		xhr.onerror = function () {
			reject({
				status: this.status,
				statusText: xhr.statusText
			});
		};
		xhr.send();
	});
}

Solution 2 - Javascript

I create a promise for the XHR. Then simply use await inside an async function to call it.

function getHTML(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.responseType = 'document';
        xhr.onload = function () {
            var status = xhr.status;
            if (status == 200) {
                resolve(xhr.response.documentElement.innerHTML);
            } else {
                reject(status);
            }
        };
        xhr.send();
    });
}

async function schemaPageHandler(){
    try {
        var parser = new window.DOMParser();
        var remoteCode = await getHTML('https://schema.org/docs/full.html');
        var sourceDoc = parser.parseFromString(remoteCode, 'text/html');
        var thingList = sourceDoc.getElementById("C.Thing");
        document.getElementById("structured-data-types").appendChild(thingList);
    } catch(error) {
        console.log("Error fetching remote HTML: ", error);
    }              
}

Solution 3 - Javascript

You get two options,

first is to use newer fetch api which is promise based, with with you can do

let response = await fetch(url);
response = await response.json();; // or text etc..
// do what you wanna do with response

Other option if you really want to use XMLHttpRequest is to promisify it

let response = await new Promise(resolve => {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.onload = function(e) {
     resolve(xhr.response);
   };
   xhr.onerror = function () {
     resolve(undefined);
     console.error("** An error occurred during the XMLHttpRequest");
   };
   xhr.send();
}) 
// do what you wanna do with response

possible full solution

(async () => {
   let response = await new Promise(resolve => {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, true);
      xhr.onload = function(e) {
        resolve(xhr.response);
      };
      xhr.onerror = function () {
        resolve(undefined);
        console.error("** An error occurred during the XMLHttpRequest");
      };
      xhr.send();
   }) 
   doTheThing(response)
})()

Solution 4 - Javascript

You can for example create an asynchronous class to use instead of the original one. It lacks some methods but it can serve as an example.

(function() {
    "use strict";
    
    var xhr = Symbol();
    
    class XMLHttpRequestAsync {
        constructor() {
            this[xhr] = new XMLHttpRequest();
        }
        open(method, url, username, password) {
            this[xhr].open(method, url, true, username, password);
        }
        send(data) {
            var sxhr = this[xhr];
            return new Promise(function(resolve, reject) {
                var errorCallback;
                var loadCallback;
                
                function cleanup()  {
                    sxhr.removeEventListener("load", loadCallback);
                    sxhr.removeEventListener("error", errorCallback);
                }
                
                errorCallback = function(err) {
                    cleanup();
                    reject(err);
                };
                
                loadCallback = function() {
                    resolve(xhr.response);
                };
                
                
                sxhr.addEventListener("load", loadCallback);
                sxhr.addEventListener("error", errorCallback);
                
                
                sxhr.addEventListener("load", function load() {
                    sxhr.removeEventListener("load", load);
                    resolve(sxhr.response);
                });
                sxhr.send(data);
            });
        }
        set responseType(value)
        {
            this[xhr].responseType = value;
        }
        setRequestHeader(header, value) {
            this[xhr].setRequestHeader(header, value);
        }
    }
    
    addEventListener("load", async function main() {
        removeEventListener("load", main);


        var xhra = new XMLHttpRequestAsync();
        xhra.responseType = "json";
        xhra.open("GET", "appserver/main.php/" + window.location.hash.substring(1));
        console.log(await xhra.send(null));
        
    });
    
}());

Solution 5 - Javascript

I had the same problem and solved it using the following function:

const makeRequest = (method, url, data = {}) => {
  const xhr = new XMLHttpRequest();
  return new Promise(resolve => {
    xhr.open(method, url, true);
    xhr.onload = () => resolve({
      status: xhr.status,
      response: xhr.responseText
    });
    xhr.onerror = () => resolve({
      status: xhr.status,
      response: xhr.responseText
    });
    if (method != 'GET') xhr.setRequestHeader('Content-Type', 'application/json');
    data != {} ? xhr.send(JSON.stringify(data)) : xhr.send();
  })
}

const test = async() => {
  console.log("Starting request ...")
  let request = await makeRequest("GET", "https://jsonplaceholder.typicode.com/todos/1");
  console.log("status:", request.status)
  console.log("response:", request.response)
}
test()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjkupczakView Question on Stackoverflow
Solution 1 - JavascriptThắng Trần XuânView Answer on Stackoverflow
Solution 2 - JavascriptRonnie RoystonView Answer on Stackoverflow
Solution 3 - JavascriptJiby JoseView Answer on Stackoverflow
Solution 4 - Javascriptuser2704215View Answer on Stackoverflow
Solution 5 - JavascriptAvraham MintzbergView Answer on Stackoverflow