How Axios Manages Internal Promise Chaining

Axios handles HTTP requests and responses through an internal Promise-based execution pipeline built on a dynamic chain array. By orchestrating request interceptors, a central transport adapter, and response interceptors into a single continuous Promise chain, Axios ensures that data transformation, asynchronous operations, and error handling flow sequentially through every step of the request lifecycle.

The Interceptor and Dispatch Pipeline

At the core of Axios's request mechanism is the Axios.prototype.request method. Historically and architecturally, Axios models its lifecycle around an internal array often referred to as the chain.

When a request is initiated, Axios initializes this pipeline with the central dispatch handler:

var chain = [dispatchRequest, undefined];

Here, dispatchRequest is the internal function responsible for invoking the underlying transport adapter (such as XMLHttpRequest in the browser or the http module in Node.js). The undefined acts as the corresponding rejection handler for the initial adapter invocation.

Registering and Ordering Interceptors

Axios allows developers to register request and response interceptors via axios.interceptors.request.use() and axios.interceptors.response.use(). Each interceptor consists of a fulfillment handler and a rejection handler.

To maintain the correct execution sequence, Axios pushes these handlers into the chain array using different array methods:

  1. Request Interceptors: Added to the front of the array using Array.prototype.unshift. This ensures that the most recently registered request interceptor executes first, processing configuration objects sequentially before reaching the dispatch adapter.
  2. Response Interceptors: Appended to the end of the array using Array.prototype.push. This ensures response interceptors execute in the order they were registered, processing the returned response data after the adapter completes.

The resulting structure forms a symmetrical queue:

[Request Interceptors] -> [dispatchRequest, undefined] -> [Response Interceptors]

Constructing the Promise Chain

Once the chain array is populated, Axios initializes a resolved Promise holding the request configuration:

var promise = Promise.resolve(config);

while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

return promise;

By shifting pairs of functions (onFulfilled and onRejected) from the array and attaching them via .then(), Axios constructs a linked Promise pipeline. The output of one step becomes the input to the next.

Synchronous vs. Asynchronous Interceptor Execution

In newer versions of Axios, the engine includes an optimization for synchronous request interceptors. If all request interceptors are marked with synchronous: true, Axios iterates through them synchronously rather than wrapping each in an asynchronous Promise tick.

If any interceptor returns a Promise or does not specify the synchronous flag, Axios falls back to assembling the asynchronous Promise chain. This optimization reduces microtask scheduling overhead in high-throughput environments while preserving standard async capabilities when needed.

Transport Dispatch and Error Propagation

When the execution reaches dispatchRequest, the configured adapter executes the network call and returns a Promise:

Because native Promise chaining guarantees error propagation, a rejection at any stage—whether inside a request interceptor, the transport adapter, or a response interceptor—bypasses subsequent fulfillment handlers and moves directly to the nearest onRejected handler or the consumer's final .catch() block.