How Axios Handles Async Requests Using Promises

Axios is a popular, promise-based HTTP client for JavaScript that streamlines making asynchronous network requests in both the browser and Node.js environments. This article explores how Axios leverages native JavaScript Promises to manage the lifecycle of HTTP operations, execute asynchronous code with clean syntax, catch network errors, and handle concurrent API calls efficiently.

The Promise-Based Core of Axios

At its core, every HTTP request method provided by Axios (such as axios.get(), axios.post(), or axios.delete()) returns a native JavaScript Promise. A Promise represents an operation that has not completed yet but is expected to produce a value or an error in the future.

When you invoke an Axios method, the request starts in a pending state. Depending on the outcome of the network interaction, Axios transitions the Promise into one of two states:

  1. Fulfilled (Resolved): If the HTTP request succeeds and returns an HTTP status code in the 2xx range, Axios resolves the Promise with a structured response object containing the payload (data), status code (status), headers, and request configuration.
  2. Rejected: If the server returns a status code outside the 2xx range, or if a network failure or timeout occurs, Axios automatically rejects the Promise with an error object.

Consuming Requests with .then() and .catch()

Because Axios returns standard Promises, you can handle responses using classic Promise chaining methods:

axios.get('https://api.example.com/data')
  .then((response) => {
    console.log('Status:', response.status);
    console.log('Data:', response.data);
  })
  .catch((error) => {
    if (error.response) {
      // The server responded with a non-2xx status code
      console.error('Server Error:', error.response.status, error.response.data);
    } else if (error.request) {
      // The request was made but no response was received
      console.error('Network Error: No response received');
    } else {
      // An error occurred setting up the request
      console.error('Request Setup Error:', error.message);
    }
  })
  .finally(() => {
    console.log('Request completed.');
  });

Using async/await for Cleaner Asynchronous Flow

Since modern JavaScript supports the async/await syntax, developers can write asynchronous Axios calls that read like synchronous code. When using await, the execution pauses inside the async function until the Axios Promise settles:

async function fetchData() {
  try {
    const response = await axios.get('https://api.example.com/data');
    console.log('Data received:', response.data);
    return response.data;
  } catch (error) {
    console.error('Failed to fetch data:', error.message);
  }
}

Managing Concurrent Requests

Axios integrates seamlessly with native Promise methods like Promise.all() to handle multiple asynchronous requests concurrently. This allows you to fire several HTTP calls simultaneously and wait until all of them resolve:

async function fetchMultipleResources() {
  try {
    const [usersResponse, postsResponse] = await Promise.all([
      axios.get('https://api.example.com/users'),
      axios.get('https://api.example.com/posts')
    ]);

    console.log('Users:', usersResponse.data);
    console.log('Posts:', postsResponse.data);
  } catch (error) {
    console.error('One or more requests failed:', error);
  }
}

Interceptors and Promise Chaining

Axios extends Promise functionality through interceptors, allowing you to run code or mutate data before a request is sent or before a response Promise resolves. Interceptors act as intermediate steps in the underlying Promise chain: