308A.4 - AJAX and Data Fetching
Learning Objectives
By the end of this lesson, learners will be able to:
- Describe the purpose of AJAX and the
XMLHttpRequest
object. - Use AJAX to make requests from a server without reloading the webpage.
- Use AJAX to receive and work with data from a server.
- Use the
fetch
API to asynchronously communicate with a server. - Use the
axios
library to asynchronously communicate with a server.
Asynchronous JavaScript and XML
Asynchronous JavaScript and XML (AJAX) is a term used to describe the use of the XMLHttpRequest (XHR) object to communicate with servers. Despite the naming conventions, AJAX and the XHR object can both be used to retrieve any type of data, not just XML.
"Asynchronous" in the context of web development is the ability to communicate with a server, exchange data (send and receive), and update the local webpage without having to fully refresh it. This allows individual parts of a webpage to update individually of one another, and - importantly - without waiting for the other portions to finish their changes.
The XMLHttpRequest Object
Using AJAX involves creating a new XHR object to begin your request:
const request = new XMLHttpRequest();
The XHR object methods for communicating with the server are open
and send
.
- The first parameter of
open
is the HTTP request method supported by the server you're communicating with. There are many possible request methods, but the most commonly used areGET
,POST
,PUT
, andDELETE
. As per the HTTP standard, request methods should be entirely capitalized. - The second parameter of
open
is the URL that the request will be sent to. It is important to note that requests cannot be made to third-party domains by default; in order to do so, you need the proper Cross-Origin Resource Sharing (CORS) headers included in the request. More on this later. - The third parameter of
open
is optional, and defines whether the request is asynchronous (defaulttrue
). Asynchronous requests allow the code on your webpage to continue executing even while waiting for the response from the server (which could take an unknown amount of time). - The
send
method takes a single parameter containing the data (if any) that you want to send to the server as part of aPOST
request.
request.open("GET", "http://www.example.com/myFile.json", true);
request.send();
If you want to POST
data, you may have to set the MIME type of the data being sent. To do so, use the setRequestHeader
method of the XHR object before your send
call.
request.setRequestHeader(
"Content-Type",
"my-MIME-type-here"
);
Now that we've learned how to send the request, how do we deal with the response?
The XHR object comes with an event listener called onreadystatechange
, which we can assign a function to handle the response as appropriate.
This event will fire each time the readyState
value of the request object changes. The value of readyState
also allows us to include specific logic within our event handler function cooresponding to the current state of the request, as follows:
- 0 (uninitialized) or (request not initialized). Client has been created.
open()
not called yet. - 1 (loading) or (server connection established).
open()
has been called. - 2 (loaded) or (request received).
send()
has been called, and headers and status are available. - 3 (interactive) or (processing request). Downloading;
responseText
holds partial data. - 4 (complete) or (request finished and response is ready). The operation is complete.
Each of these values also cooresponds to a static property of the XMLHttpRequest
object, which can be used to make our code more readable:
- 0:
XMLHttpRequest.UNSENT
- 1:
XMLHttpRequest.OPENED
- 2:
XMLHttpRequest.HEADERS_RECEIVED
- 3:
XMLHttpRequest.LOADING
- 4:
XMLHttpRequest.DONE
function handleResponse() {
if (request.readyState === XMLHttpRequest.DONE) {
// request is complete; do tasks.
} else {
// request is not complete.
}
}
request.onreadystatechange = handleResponse;
The two options for accessing the response data are request.responseText
, which contains the response as a string of text, and request.responseXML
, which contains the response as an XMLDocument
object that can be traversed with JavaScript DOM functions.
Simple AJAX Example
Here, we'll discuss a simple example that demonstrates what we've discussed so far. Feel free to follow along by creating two files within the same directory, as described below.
test.html
This is a test!
index.html
<button id="myBtn" type="button">Test it Out</button>
<script>
let request;
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
function testRequest() {
request = new XMLHttpRequest();
if (!request) {
alert("Failed to create an XMLHttpRequest Object.");
return false;
}
request.onreadystatechange = alertResponse;
request.open("GET", "test.html");
request.send();
}
function alertResponse() {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status === 200) {
alert(request.responseText);
} else {
alert("The request returned a status other than 200 OK: " + request.status);
}
}
}
</script>
Here, we create a button that calls testRequest
when clicked, which creates a request handled by alertResponse
. If the reponse returns a status of 200 (which means OK), we send an alert with the text contents of the response.
We've added the extra step of checking the response status in this example, which is good practice. See this reference for a list of potential response statuses. Note that because we are accessing the response status, an exception may be thrown in the event that there is a communication error (like the server being unavailable). You can mitigate this by wrapping your logic in a try...catch
statement.
Working with XML
The previous example shows how to easily display contents as text, but most data is more complex. If we converted test.html
into a proper XML file and named it test.xml
, we could handle the response with request.responseXML
and make use of familiar JavaScript DOM methods.
test.xml
<? xml version="1.0">
<root>XML data test!</root>
index.html
<button id="myBtn" type="button">Test it Out</button>
<script>
let request;
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
function testRequest() {
request = new XMLHttpRequest();
if (!request) {
alert("Failed to create an XMLHttpRequest Object.");
return false;
}
request.onreadystatechange = alertResponse;
request.open("GET", "test.xml"); // Change this to point to our new XML document.
request.send();
}
function alertResponse() {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status === 200) {
// Change the way we handle the response data.
const xmlDoc = request.responseXML;
const doc_root = xmlDoc.querySelector("root");
let data = doc_root.firstChild.data;
alert(data);
} else {
alert("The request returned a status other than 200 OK: " + request.status);
}
}
}
</script>
While this is still a simple example, working with XML gives us the opportunity to navigate more complex data responses with ease.
Posting Data
Assume you have a form with some data that you want sent to your server for processing. While this can be done with pure HTML, AJAX allows us to process the data, receive a response, and update portions of the page without refreshing it. This provides a much smoother, more responsive user experience.
Let's modify the example we've been working with to send data from a form. First, add the following simple text input field:
index.html
<label>
Input:
<input type="text" id="myInput" />
</label>
<!-- Previous code omitted below -->
We also need to ensure this data gets sent in our request, so we need to modify both the open
and send
method calls. For this example, we'll be using a free fake API located at "https://jsonplaceholder.typicode.com/todos." You can click on this link to see how the data we receive will be structured.
request.open("POST", "https://jsonplaceholder.typicode.com/todos");
request.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
let inputVal = document.getElementById("myInput").value;
let encodedVal = encodeURIComponent(inputVal);
request.send(`data=${encodedVal}`);
Since this is a POST
request, the status we get back also might be different. If we test our code now, we'll see that receive a status of 201 Created
. This is still a successful response, so we'll want to modify the way our code checks status. While we're at it, we'll also change the way we read the response to .responseText
.
if (request.status === 200 || request.status === 201) {
alert(request.responseText);
// omitted other code
Another test would reveal that we're getting a response that contains a JSON object that looks like this:
{
"data": "Testing",
"id": 201
}
Our POST
request has created a new object on the server with a data
value of whatever we entered into our text field and an unique id
field. Since this is a fake API, these changes won't actually occur in the external database, but in the case of a real API we would then be able to GET
our newly created resource, or modify it and existing resources with PUT
, PATCH
, DELETE
, and so on.
If we needed to parse this new object, how might we go about doing so?
No matter what language or technology is being used by a server, the client has the tools to handle requests and responses. It is important that you research any APIs you may access during your projects, as the only thing that is guaranteed is that no two APIs are identical. Formatting, data types, response times... everything can vary.
There are some tools and interfaces that can make these processes simpler and more intuitive, including the Fetch API and Axios, which we will discuss in this lesson.
The Fetch API
The Fetch API is a more powerful and flexible replacement for the XMLHttpRequest
object. These two options for communication are not the only ones, and you will likely run into many other tools, interfaces, and libraries that accomplish similar goals as you continue learning. The fundamentals, however, remain the same.
The fetch()
method, which is a global method available in both the Window
and Worker
contexts (available to use almost anywhere), takes a single argument in its simplest form:
async function logJSONData() {
const response = await fetch("http://www.example.com/something.json");
const jsonData = await response.json();
console.log(jsonData);
}
fetch()
does not directly return the JSON response body, but instead a Promise
that resolves with a Response
object. The Response
object is a representation of the entire HTTP response, so the json()
method is used to extract the JSON body content from the Response
(which returns a second Promise
that resolves with the parsed JSON data).
The Response
object has other instances methods that you should reference when needed:
-
Response.arrayBuffer()
- Returns a promise that resolves with an
ArrayBuffer
representation of the response body.
- Returns a promise that resolves with an
-
Response.blob()
- Returns a promise that resolves with a
Blob
representation of the response body.
- Returns a promise that resolves with a
-
Response.clone()
- Creates a clone of a
Response
object.
- Creates a clone of a
-
Response.formData()
- Returns a promise that resolves with a
FormData
representation of the response body.
- Returns a promise that resolves with a
-
Response.json()
- Returns a promise that resolves with the result of parsing the response body text as JSON.
-
Response.text()
- Returns a promise that resolves with a text representation of the response body.
The second parameter to the fetch()
method is an options object containing any custom settings that you want to apply to the request. For a full list of the available options, see the MDN reference on fetch()
parameters.
The options that coorespond to our XMLHttpRequest
object examples include:
method
: The request method, e.g.,GET
,POST
.headers
: Any headers you want to add to your request, contained within aHeaders
object or an object literal with string values.body
: Any body that you want to add to your request: this can be aBlob
, anArrayBuffer
, aTypedArray
, aDataView
, aFormData
, aURLSearchParams
, string object or literal, or aReadableStream
object.
Here's an example of how we can use fetch()
to connect to an external API and show a random image of a dog:
<img id="dog" src="" height="250" style="cursor: pointer" />
<script>
const dog = document.getElementById('dog');
dog.addEventListener("click", getNewDog);
async function getNewDog() {
dog.style.cursor = 'wait';
const response = await fetch("https://dog.ceo/api/breeds/image/random");
const jsonData = await response.json();
const url = jsonData.message;
dog.src = url;
dog.style.cursor = 'pointer';
}
getNewDog();
</script>
Here is that example in practice; click! Aren't they adorable?
Posting Data with Fetch
Many requests also need to send data, so let's use our previous example to see how we can post data to an API with fetch()
. Here are the two methods side by side:
XMLHttpRequest
Fetch
<label>
Input:
<input type="text" id="myInput" />
</label>
<button id="myBtn" type="button">Test it Out</button>
<script>
let request;
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
function testRequest() {
request = new XMLHttpRequest();
if (!request) {
alert("Failed to create an XMLHttpRequest Object.");
return false;
}
request.onreadystatechange = alertResponse;
request.open("POST", "https://jsonplaceholder.typicode.com/todos");
request.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded"
);
let inputVal = document.getElementById("myInput").value;
let encodedVal = encodeURIComponent(inputVal);
request.send(`data=${encodedVal}`);
}
function alertResponse() {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status === 200 || request.status === 201) {
alert(request.responseText);
} else {
alert("The request returned a status other than 200 OK: " + request.status);
}
}
}
</script>
<label>
Input:
<input type="text" id="myInput" />
</label>
<button id="myBtn" type="button">Test it Out</button>
<script>
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
async function testRequest() {
let inputVal = document.getElementById("myInput").value;
let requestBody = { data: inputVal };
const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
}
});
alertResponse(response);
}
async function alertResponse(response) {
if (response.ok) {
const textData = await response.text();
alert(textData);
} else {
alert("The request returned a status other than 200 OK: " + response.status);
}
}
</script>
As you can see, using fetch()
alongside async/await
and promises results in code that is easier to implement and understand.
Fetch is considered the modern replacement for XMLHttpRequest
, so why do we even teach the latter?
Firstly, foundational tools and concepts are always a good place to start. Secondly, the fetch API is not supported on all browsers by default. As always when working with modern tools, libraries, interfaces, etc., you should verify the compatibility of anything you're uncertain about with Can I Use?. Check the link to see where Fetch
and the fetch API are supported by default.
Thankfully, fetch is compatible with almost all modern browsers. In front-end development, always make sure you understand the compatibility requirements of whatever applications you are working on.
Axios
Axios is another very commonly used alternative to XMLHttpRequest
and fetch()
, though it is important to note that Axios has wider browser support than fetch()
because it uses XMLHttpRequest
under the hood. However, Axios is not native to some browsers like fetch()
is, so it needs to be manually installed or imported into any projects you use it with. For instructions on how to do so, see the Axios documentation on getting started.
For the purposes of our examples, we'll be using the jsDelivr CDN:
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Axios lists the following features within its documentation:
- Make
XMLHttpRequests
from the browser - Make
http
requests from node.js - Supports the
Promise
API - Intercept request and response
- Transform request and response data
- Cancel requests
- Timeouts
- Query parameters serialization with support for nested entries
-
Automatic request body serialization to:
- JSON (
application/json
) - Multipart / FormData (
multipart/form-data
) - URL encoded form (
application/x-www-form-urlencoded
)
- JSON (
- Posting HTML forms as JSON
- Automatic JSON data handling in response
- Progress capturing for browsers and node.js with extra info (speed rate, remaining time)
- Setting bandwidth limits for node.js
- Compatible with spec-compliant
FormData
andBlob
(including node.js) - Client side support for protecting against
XSRF
(cross-site request forgery)
The simplest syntax for making an Axios request is axios(url)
, but there are many configuration options that can be used to change the behavior of a request. These configuration options can be passing into an axios()
call through an object containing one or more properties, as described below (taken from the Axios documentation). The only mandatory property is the url
.
{
// `url` is the server URL that will be used for the request
url: '/user',
// `method` is the request method to be used when making the request
method: 'get', // default
// `baseURL` will be prepended to `url` unless `url` is absolute.
// It can be convenient to set `baseURL` for an instance of axios to pass relative URLs
// to methods of that instance.
baseURL: 'https://some-domain.com/api',
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
transformRequest: [function (data, headers) {
// Do whatever you want to transform the data
return data;
}],
// `transformResponse` allows changes to the response data to be made before
// it is passed to then/catch
transformResponse: [function (data) {
// Do whatever you want to transform the data
return data;
}],
// `headers` are custom headers to be sent
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
// NOTE: params that are null or undefined are not rendered in the URL.
params: {
ID: 12345
},
// `paramsSerializer` is an optional function in charge of serializing `params`
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function (params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'
// When no `transformRequest` is set, must be of one of the following types:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - Browser only: FormData, File, Blob
// - Node only: Stream, Buffer
data: {
firstName: 'Fred'
},
// syntax alternative to send data into the body
// method post
// only the value is sent, not the key
data: 'Country=Brasil&City=Belo Horizonte',
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout: 1000, // default is `0` (no timeout)
// `withCredentials` indicates whether or not cross-site Access-Control requests
// should be made using credentials
withCredentials: false, // default
// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
adapter: function (config) {
/* ... */
},
// `auth` indicates that HTTP Basic auth should be used, and supplies credentials.
// This will set an `Authorization` header, overwriting any existing
// `Authorization` custom headers you have set using `headers`.
// Please note that only HTTP Basic auth is configurable through this parameter.
// For Bearer tokens and such, use `Authorization` custom headers instead.
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` indicates the type of data that the server will respond with
// options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser only: 'blob'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses (Node.js only)
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` allows handling of progress events for uploads
// browser only
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` allows handling of progress events for downloads
// browser only
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `maxContentLength` defines the max size of the http response content in bytes allowed in node.js
maxContentLength: 2000,
// `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
maxBodyLength: 2000,
// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
// rejected.
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` defines the maximum number of redirects to follow in node.js.
// If set to 0, no redirects will be followed.
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
socketPath: null, // default
// `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
// and https requests, respectively, in node.js. This allows options to be added like
// `keepAlive` that are not enabled by default.
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// `proxy` defines the hostname, port, and protocol of the proxy server.
// You can also define your proxy using the conventional `http_proxy` and
// `https_proxy` environment variables. If you are using environment variables
// for your proxy configuration, you can also define a `no_proxy` environment
// variable as a comma-separated list of domains that should not be proxied.
// Use `false` to disable proxies, ignoring environment variables.
// `auth` indicates that HTTP Basic auth should be used to connect to the proxy, and
// supplies credentials.
// This will set an `Proxy-Authorization` header, overwriting any existing
// `Proxy-Authorization` custom headers you have set using `headers`.
// If the proxy server uses HTTPS, then you must set the protocol to `https`.
proxy: {
protocol: 'https',
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` specifies a cancel token that can be used to cancel the request
// (see Cancellation section below for details)
cancelToken: new CancelToken(function (cancel) {
}),
// `decompress` indicates whether or not the response body should be decompressed
// automatically. If set to `true` will also remove the 'content-encoding' header
// from the responses objects of all decompressed responses
// - Node only (XHR cannot turn off decompression)
decompress: true // default
}
Axios requests can be made in a number of ways using convenient aliases:
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
axios.postForm(url[, data[, config]])
axios.putForm(url[, data[, config]])
axios.patchForm(url[, data[, config]])
Note that the url
, method
, and data
properties do not need to be specified within the config
object when using an alias that includes them elsewhere.
Axios vs. Fetch
Let's compare Axios and fetch()
to better understand why we would use one or the other.
Basic Syntax
- The basic syntax of each is very similar, with mostly small differences.
- Axios automatically serializes data when sending JavaScript objects to the server using
POST
; fetch does not. - Axios makes the response available as JSON by default, whereas with fetch you much use the
.json()
method to acquire the body of the response. - Axios will automatically reject a promise if it receives an HTTP status code outside of the range of success (200 - 299), allowing us to handle HTTP errors in
catch()
blocks. Fetch will still resolve unsuccessful responses, which means that HTTP errors need to be handled separately.
Here is our previous fetch()
example alongside an Axios example that accomplishes the same task. Each has been slightly modified to log JSON data instead of alerting text data.
Fetch
Axios
<label>
Input:
<input type="text" id="myInput" />
</label>
<button id="myBtn" type="button">Test it Out</button>
<script>
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
async function testRequest() {
let inputVal = document.getElementById("myInput").value;
let requestBody = { data: inputVal };
const response = await fetch('https://jsonplaceholder.typicode.com/todos', {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
}
});
logResponse(response);
}
async function logResponse(response) {
if (response.ok) {
const jsonData = await response.json();
console.log(jsonData);
} else {
console.log("The request returned a status other than 200 OK: " + response.status);
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<label>
Input:
<input type="text" id="myInput" />
</label>
<button id="myBtn" type="button">Test it Out</button>
<script>
document
.getElementById("myBtn")
.addEventListener("click", testRequest);
async function testRequest() {
let inputVal = document.getElementById("myInput").value;
let requestBody = { data: inputVal };
const response = await axios.post('https://jsonplaceholder.typicode.com/todos',
requestBody, {
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}
});
logResponse(response);
}
async function logResponse(response) {
console.log(response.data);
}
</script>
If we log response
instead of response.data
, we will see that Axios packages its response object much differently from fetch. It has the following information:
{
// `data` is the response that was provided by the server
data: {},
// `status` is the HTTP status code from the server response
status: 200,
// `statusText` is the HTTP status message from the server response
// As of HTTP/2 status text is blank or unsupported.
// (HTTP/2 RFC: https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.4)
statusText: 'OK',
// `headers` the HTTP headers that the server responded with
// All header names are lower cased and can be accessed using the bracket notation.
// Example: `response.headers['content-type']`
headers: {},
// `config` is the config that was provided to `axios` for the request
config: {},
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance in the browser
request: {}
}
Response Timeout
- Axios has the ability to easily setup a response timeout, while fetch does not.
- Response timeouts are essential in web development. Without an appropriate timeout, the request can hang and slow down your application.
Here is how we easily set up a response timeout with Axios by adding a timeout
property to our configuration object. The timeout
property's value is a number in miliseconds.
axios.get(url, {
...
timeout: 5000,
...
});
While it is possible to create this functionality with fetch, doing so is beyond the scope of this lesson.
Intercepting Requests and Responses
- Interceptors allow us to create global behavior for all requests and responses.
- Interceptors fire between actions. A request interceptor can modify a request before it is actually sent, and a response interceptor can modify the response before it is handled by a
then()
orcatch()
statement.
This is best described with examples. First, let's look at how to build a simple interceptor that logs a message to the console every time a request is sent:
axios.interceptor.request.use(request => {
console.log('Request sent.');
return request;
});
If we wanted to log messages for our server responses, we could do something similar using the .response
property:
axios.interceptor.response.use(
(response) => {
// Success: status 200 - 299
console.log('Successful response!');
return response;
},
(error) => {
// Failure: anything outside of status 2XX
console.log('Unsuccessful response...');
throw error;
}
);
If you need to dynamically create and remove interceptors within your application, you can. The method for removing an interceptor, .eject
, requires that interceptor to be assigned to a variable:
const requestInterceptor = axios.interceptor.request.use(request => {
console.log('Request sent.');
return request;
});
axios.interceptor.request.eject(requestInterceptor);
Let's discuss some more practical use cases. What if we wanted to time every request we made? We could create request and response interceptors to incorporate that data into our request and response objects:
axios.interceptors.request.use(request => {
request.metadata = request.metadata || {};
request.metadata.startTime = new Date().getTime();
return request;
});
axios.interceptors.response.use(
(response) => {
response.config.metadata.endTime = new Date().getTime();
response.config.metadata.durationInMS = response.config.metadata.endTime - response.config.metadata.startTime;
console.log(`Request took ${response.config.metadata.durationInMS} milliseconds.`)
return response;
},
(error) => {
error.config.metadata.endTime = new Date().getTime();
error.config.metadata.durationInMS = error.config.metadata.endTime - error.config.metadata.startTime;
console.log(`Request took ${error.config.metadata.durationInMS} milliseconds.`)
throw error;
});
We'll take a moment here to note that while we're using request
and response
to make it very clear what we're dealing with, you will often see the variables req
and res
to coorespond to these two items in a true development environment.
If we wanted this information to be immediately available to our handler functions, we could place it directly within our response object instead of in a metadata field. We could then access it easily through object destructuring:
axios.interceptors.request.use(request => {
request.metadata = request.metadata || {};
request.metadata.startTime = new Date().getTime();
return request;
});
axios.interceptors.response.use(
(response) => {
response.config.metadata.endTime = new Date().getTime();
response.durationInMS = response.config.metadata.endTime - response.config.metadata.startTime;
return response;
},
(error) => {
error.config.metadata.endTime = new Date().getTime();
error.durationInMS = error.config.metadata.endTime - error.config.metadata.startTime;
throw error;
});
(async () => {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const { data, durationInMS } = await axios(url);
console.log(`Request took ${durationInMS} milliseconds.`);
console.log(data);
})();
Some other common usages for Axios interceptors include server authentication handling, especially when authentication tokens need to be periodically refreshed without having the user re-authenticate every time; and retrying failed requests a specified number of times. Like most common tasks in programming, the latter has already been packaged into multiple plugins: axios-retry and retry-axios.
It's important to note that you can do anything with fetch()
that you can with Axios. Axios does not create new possibilities, it simply gives you access to pre-made, streamlined solutions to common problems. If you wanted to, you could implement the entirety of Axios's functionality using the fetch API, but it would be more difficult and time-consuming to do so.
Practicing Asynchronous JavaScript
These concepts are best cemented through practice and practical application:
ALAB 308A.4.1 - AJAX, Fetch, and Axios will give you tasks to complete using the tools discussed in this lesson.