CodeVetter · Repo Unpack · Behavior traces
axios/axios
Three flows dominate: module load/instance creation, a Node GET through the HTTP adapter, and a browser POST through XHR with XSRF. A fourth worth tracing is cancellation, which has to unwind listeners at every stage. Interceptor ordering is the subtle part — request interceptors currently default to LIFO via the `legacyInterceptorReqResOrdering: true` transitional flag, and the whole chain has a synchronous fast path that bypasses Promise wrapping when every request interceptor declares `synchronous: true`.
- Startup trace: `index.js` → `lib/axios.js` module body → `createInstance(defaults)` → `new Axios(defaults)` (constructs two `InterceptorManager`s) → `bind(Axios.prototype.request, context)` → `utils.extend` twice → statics (`Axios`, `AxiosError`, `CancelToken`, `all`, `spread`, `mergeConfig`, `AxiosHeaders`, `formToJSON`, `getAdapter`, `HttpStatusCode`) attached, `axios.default = axios`.
- Request entry trace: `Axios.request` is an `async` wrapper that awaits `_request` and, on error, decorates `err.stack` with a freshly captured stack (via `Error.captureStackTrace` into a dummy, slicing off the `Error:` line) — all inside a nested try/catch so a throwing custom stack hook can never replace the original failure.
- `_request` order is: normalize string URL → `mergeConfig(this.defaults, config)` → assert `transitional` options → normalize function `paramsSerializer` into `{serialize}` → resolve `allowAbsoluteUrls` (config → instance default → `true`) → spelling checks (`baseUrl`→`baseURL`, `withXsrfToken`→`withXSRFToken`) → lowercase method via `utils.getSafeProp` → flatten headers.
- Interceptor chain assembly: request interceptors with a `runWhen(config) === false` are skipped; `synchronousRequestInterceptors` is AND-ed across the remainder; with `transitional.legacyInterceptorReqResOrdering` truthy (the default) each handler pair is `unshift`ed producing LIFO, otherwise `push`ed producing FIFO. Response interceptors are always pushed (FIFO).
- Async path builds `[...requestChain, dispatchRequest.bind(this), undefined, ...responseChain]` and folds it with `promise.then(chain[i++], chain[i++])`; the synchronous path instead calls each `onFulfilled` inline, and if an `onRejected` returns a thenable it resumes with `dispatchRequest.call(this, newConfig)` after it settles.
- Node GET trace: `dispatchRequest` → `adapters.getAdapter(['xhr','http','fetch'])` picks `httpAdapter` (xhr is `false` in Node) → `wrapAsync(dispatchHttpRequest)` → `own()` reads ~20 config keys → `buildFullPath` → `new URL(fullPath, urlBase)` → protocol allow-list check against `platform.protocols` → `AxiosHeaders.from(...).normalize()` → default `User-Agent: axios/<VERSION>` → body encoding → `options` assembled on `Object.create(null)`.
- Transport selection inside the Node adapter is a four-way branch: `httpVersion === 2` → `http2Transport`; else `config.transport` if set; else `maxRedirects === 0` → native `http`/`https` (`isNativeTransport = true`); else `follow-redirects`' `httpFollow`/`httpsFollow` (the only transport that enforces `options.maxBodyLength` itself).
- Response handling trace in Node: `handleResponse(res)` → optional `AxiosTransformStream` for download progress/rate → decompression stream pushed per `content-encoding` → `stream.pipeline(streams)` → for `responseType: 'stream'` an async generator enforces `maxContentLength` lazily; otherwise chunks buffer with a running byte count, `end` concatenates + `stripBOM`, then `settle`.
- Timeout trace in Node: `createTimeoutError()` picks `ETIMEDOUT` when `transitional.clarifyTimeoutError` is true and `ECONNABORTED` otherwise; for the native-transport path an extra wall-clock `connectPhaseTimer` is armed because `ClientRequest#setTimeout` may not fire during TCP connect.
- Cancellation trace: `dispatchRequest` pre-checks `cancelToken.throwIfRequested()` and `signal.aborted`; the Node adapter then subscribes `abort` to both, emits on an internal `EventEmitter`, and `onFinished()` unsubscribes the token, removes the `abort` listener, clears the connect timer, and calls `abortEmitter.removeAllListeners()` — deferred until a streamed response body finishes via `stream.finished`.
- Browser POST trace: `xhrAdapter` → `resolveConfig(config)` (merge, build URL, basic auth, FormData content-type handling, XSRF cookie read) → `new XMLHttpRequest()` → `request.open(method, url, true)` → header set from `toByteStringHeaderObject` → `onloadend` builds the response and calls `settle` with `done()` wrappers that flush progress and unsubscribe cancellation.
- XHR `onloadend` has a status-0 guard added for Firefox 152: if status is 0 and the scheme is not `file:` (checked against the request URL, the page origin, and `responseURL`), it rejects with `ECONNABORTED` instead of letting `settle` resolve an empty success.
- Fetch error-classification trace runs a strict precedence in the catch block: composed-signal AxiosError reason (preserving timeout vs cancel) → `pendingBodyError` → `pendingResponseError` → re-throw an already-`AxiosError` → `TypeError` matching /Load failed|fetch/ becomes `ERR_NETWORK` with non-enumerable `cause` → otherwise `AxiosError.from`.
- Proxy resolution trace: `setProxy` uses `config.proxy` if set, else `getProxyForUrl(location)` from `proxy-from-env` — but skips env lookup entirely when the selected agent already has Node's native `proxyEnv` (Node ≥22.21 / ≥24.5 / >24), then either installs a cached CONNECT-tunneling `HttpsProxyAgent` for HTTPS targets or rewrites `options.hostname`/`host`/`path` for forward-proxy plaintext HTTP.