DEV Community

John Au-Yeung
John Au-Yeung

Posted on

Using AJAX and JSON in JavaScript

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

This is the JSON data that’s parsed by the browser and can be inserted to the DOM of a web page. All AJAX is sending data via HTTP requests and then response will be obtained from the the server, then the data is populated on the page via manipulating the DOM. Most modern browsers work the same way and support AJAX.

Same-Origin Policy

In most cases, requests are made to a server that has a different domain from the originating server. If we do that without setting some options, we will get an error since most browsers by default stop cross-origin requests from being made without setting specific options on the server and the client. The purpose of this is to prevent browsers from downloading code that’s unknown to the browser. This policy applies everywhere, even on our local computer, since it’s can be used to compromise the security of our computers no matter where the data is downloaded from.

To allow requests to request between computers with different domains from a web browser. The server has to enable Cross-Origin Resource Sharing, or CORS for short. This has to be enabled on the server side. If we look at a cross-origin HTTP request, we should see an OPTIONS request before the actual request is made and in we should see the following in the response headers:

Access-Control-Allow-Origin: *

Alternatively, we can put the client and server-side code in the same domain. This wouldn’t violate the same-origin policy in browsers since the request from the browser is made from the same domain from the server.

Using the XMLHttpRequest Object

We can use the XMLHttpRequest object to make a request to a server and retrieve its response. This can be done with a simple example. In this example, we will load content from a text file and then put the content inside a page. Create a file called index.html and add:

<html>  
  <head>  
    <title>XMLHttpRequest</title>  
  </head>  
  <body>  
    <h1>XML HTTP Request</h1>  
    <button id="load">Load Data</button>  
    <div id="content"></div>  
    <script src="script.js"></script>  
  </body>  
</html>

The create a file called script.js and add:

const reqListener = response => {  
  const content = document.getElementById("content");  
  content.innerHTML = response.currentTarget.response;  
};

const loadData = () => {  
  const req = new XMLHttpRequest();  
  req.onload = reqListener;  
  req.open("get", "file.txt", true);  
  req.send();  
};

window.onload = () => {  
  const loadButton = document.getElementById("load");  
  loadButton.onclick = loadData;  
};

Then in file.txt we put:

Hello world.

In the above example, we created an instance of the XMLHttpRequest object to make a GET request to our server, which is a local web server that servers the file.txt file. We call the open function to start the request, The first argument is the HTTP request method, which can be get, post, put, patch or delete . The second argument is the URL or relative path to your server-side resource. The third argument is for setting whether the HTTP request happens asynchronously. Usually, it should be true since we do not want to hold up other parts of our JavaScript code from loading. Also, we set the req.onload listener function to get the response from the server and then populate the result. In reqListener function, we get the response from the server when it’s available and then populate the content of the element with ID content with it. This happens after we call end send function on the req object.

Once we have these files we should get ‘Hello world’ when we click ‘Load Data’, we should get ‘Hello world.’ in our page.

This is the simplest case for making HTTP requests and then getting the result with AJAX. If we want to make requests that are more complex, then this is a very cumbersome way to do it. Fortunately, we have the Fetch API. to make a HTTP requests in our client side applications.

Promises

In JavaScript, a lot of code are asynchronous since it’s single threaded. To prevent holding up a program by code that finishes running in a indeterminate amount of time, we have to make a lot code asynchronous to prevent holding up the rest of the program from executing.

A promise is an object that represents an asynchronous process that finishes in an indeterminate amount of time. Before it’s executed then its status is pending. It can end in 2 states. If it’s successful, then the promise is resolved. A resolved promised is in the fulfilled state. Otherwise, it ends by by rejecting the promise. A promise is rejected if an error occurred. If an error occurred then the value that’s returned in the promise is ignored. In either case, when the promise finishes executing, a promise is in the settled status. That means settled status includes both fulfilled and error status.

The benefit of using promises for writing asynchronous code is that we can chain multiple promises together as if they are synchronous code. However, promises can only return other promises and they cannot return anything else. They are chained with the then function, which takes a callback function which has the resolved value when the promise is fulfilled. When an errors occurred they can be handled with the catch function at the end. To execute code no matter what the result of a promise is, we can chain the finally function to the end of a promise.

To run an array of promises, we can use the Promise.all function, which takes an array of promises.

An example of a promise would be something like:

const getTextPromise = new Promise((resolve, reject) => {  
  const xhr = new XMLHttpRequest();  
  xhr.open('GET', 'file.txt', true);  
  xhr.onload = (response) => resolve(response.currentTarget.responseText);  
  xhr.onerror = () => reject(xhr.statusText);  
  xhr.send();  
});

We wrapped the example we have above inside a promise and we resolve the response text instead of populating it in the DOM directly. To populate it in the DOM, we do:

getTextPromise  
.then(responseText =>{  
  const content = document.getElementById("content");  
  content.innerHTML = responseText;  
})  
.catch(error =>{  
  alert(error.toString());  
})

Make HTTP Requests with the Fetch API

The Fetch API makes use of promises to make asynchronous HTTP requests from the browser to a server. It provides us with an easy to use API for making requests. It lets us create HTTP requests with a Request object, then when the server makes a response, then we get a Response object. We make a request with the fetch method. The fetch method takes one mandatory, which includes all the data for making requests. Once the Response object is returned then we can massage it to get it to the form we want and then populate it in our web pages.

Also, it’s aware of concepts like CORS, so we can make cross-origin requests if the server allows by setting some options within the argument of the fetch method. To use the fetch method for making HTTP request, we can do it with an example. If we want to make a form for letting users subscribe to your email list, we can write the following code. Create a file calledindex.html and add:

<html>  
  <head>  
    <title>Email List Subscribe Form</title>  
  </head>  
  <body>  
    <h1>Email List Subscribe Form</h1>  
    <form action="" name="nameForm" id="nameForm" method="post">  
      <label for="firstName">First Name: </label>  
      <input type="text" name="firstName" id="firstName" /><br />  
      <label for="lastName">Last Name: </label>  
      <input type="text" name="lastName" id="lastName" /><br />  
      <label for="email">Email: </label>  
      <input type="text" name="email" id="email" /><br />  
      <input type="submit" value="Subscribe" />  
    </form>  
    <script src="script.js"></script>  
  </body>  
</html>

to add the form. Then create a script.js file in the same folder and add:

const APIURL = "http://localhost:3000";
const subscribe = data => {  
  return fetch(`${APIURL}/subscribers`, {  
    method: "POST",  
    mode: "cors",  
    cache: "no-cache",  
    headers: {  
      "Content-Type": "application/json"  
    },  
    body: JSON.stringify(data)  
  })
  .then(response => response.json());  
};

window.onload = () => {  
  const nameForm = document.forms.nameForm;  
  nameForm.method = "post";  
  nameForm.target = "_blank";  
  nameForm.action = "";  
  nameForm.addEventListener("submit", e => {  
    e.preventDefault();  
    const firstName = document.getElementById("firstName").value;  
    const lastName = document.getElementById("lastName").value;  
    const email = document.getElementById("email").value;  
    let errors = [];  
    if (!firstName) {  
      errors.push("First name is required.");  
    } 

    if (!lastName) {  
      errors.push("Last name is required.");  
    } 

    if (!email) {  
      errors.push("Email is required.");  
    } 

    if (!/\[^@\]+@\[^\.\]+\..+/.test(email)) {  
      errors.push("Email is invalid.");  
    } 

    if (errors.length > 0) {  
      alert(errors.join(" "));  
      return;  
    }  
    subscribe({  
      firstName,  
      lastName,  
      email  
    })
    .then(response => {  
      alert(`${response.firstName} ${response.lastName} has subscribed`);  
    });  
  });  
};

The subscribe function is where we used the fetch method. In there we pass in the URL of the server we are making the request to as the first argument. In the second argument, we set the method, which the HTTP request method for the request we want to make, which is POST. mode should be CORS since we’re making a cross domain request. We don’t want any request caching so we set cache to no-cache . In headers , we set Content-Type to application/json to make the requests data type send JSON. The body is the request body of the request, which we create a JSON object since we are sending JSON to the server. Finally, we return response.json() , which is a promise with the response if the request is successful sent back from the server and converted to JSON format.

To send something to the URL we point to we have to set up a web server. We first install the json-server package by running npm i json-server. Then, go to our project folder and run:

json-server --watch db.json

In db.json, change the text to:

{  
  "subscribers": []  
}

So we have the subscribers endpoints defined in the requests.js available.

Shorten Promise Code with Async and Await

The code above makes use of several promises. We have one for making the request, and then another one for converting it to JSON. Then finally, we make the alert at the end. With the then function, we make to put callback functions as an argument of all of our then functions. This makes the code long is we have lots of promises. Instead, we can use the async and await syntax to replace the then and its associated callbacks as follows. In script.js, we put:

const APIURL = "http://localhost:3000";
const subscribe = async data => {  
  const response = await fetch(`${APIURL}/subscribers`, {  
    method: "POST",  
    mode: "cors",  
    cache: "no-cache",  
    headers: {  
      "Content-Type": "application/json"  
    },  
    body: JSON.stringify(data)  
  });  
  return response.json();  
};

window.onload = () => {  
  nameForm.method = "post";  
  nameForm.target = "_blank";  
  nameForm.action = "";  
  nameForm.addEventListener("submit", async e => {  
    e.preventDefault();  
    const firstName = document.getElementById("firstName").value;  
    const lastName = document.getElementById("lastName").value;  
    const email = document.getElementById("email").value;  
    let errors = [];  
    if (!firstName) {  
      errors.push("First name is required.");  
    } 

    if (!lastName) {  
      errors.push("Last name is required.");  
    } 

    if (!email) {  
      errors.push("Email is required.");  
    } 

    if (!/\[^@\]+@\[^\.\]+\..+/.test(email)) {  
      errors.push("Email is invalid.");  
    } if (errors.length > 0) {  
      alert(errors.join(" "));  
      return;  
    }  
    try {  
      const response = await subscribe({  
        firstName,  
        lastName,  
        email  
      });  
      alert(`${response.firstName} ${response.lastName} has subscribed`);  
    } catch (error) {  
      alert(error.toString());  
    }  
  });  
};

We replaced the then and callbacks with await . Then we can assign the resolved values of each promise as variables. Note that if we use await for our promise code then we have to put async in the function signature like we did in the above example. To catch errors, instead of chaining the catch function at the end, we use the catch clause to do it instead. Also, instead of chaining the finally function at the bottom to run code when a promise ends, we use the finally clause after the catch clause to do that instead.

async functions always return promises, and cannot return anything else like any other function that uses promises.

With the Fetch API and async and await syntax, we can make HTTP requests to servers much more easily than using the old XMLHttpRequest object. This is why now more and more web applications are using AJAX instead of refreshing the whole page on server side to get data.

Top comments (0)