项目:my-ts-axios | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
第 02 篇《模块设计》回答的是"模块怎么划分、为什么这样划分";本篇则下沉一层,对 src/ 下的每一个源文件做逐文件深读:文件职责、导出清单、逐函数机制走读与文件间依赖关系。阅读本篇时建议同时打开对应源文件,行号引用以当前版本(package.json version 0.2.1)为准,重构后请以函数名为锚重新定位。
全篇按第 00 篇确立的四组划分组织:入口组(src/index.ts、src/axios.ts、src/defaults.ts)、编排组(src/core/ 除 adapters/ 外)、端点组(src/core/adapters/ 三适配器)、支撑组(src/helpers/、src/cancel/、src/shims/、src/types/)。每个文件给出三样东西:一句话职责、导出清单表格、逐函数机制说明。行数统计来自当前源码,用于建立"该文件有多重"的直觉。
一个贯穿全篇的阅读原则:这个库的函数几乎都是"无状态纯函数 + 一个大闭包(适配器)"的形态,理解任何一个文件只需要回答三个问题——输入是什么、输出是什么、修改了谁的引用。第 04 篇已经证明"全程只有一个 config 对象被就地修改",本篇会在每个函数的走读中反复标注"这里改的是引用还是副本",这是排查该库行为问题时最重要的单一线索。
库的唯一出口。import axios from './axios' 后执行两条导出:export * from './types'(全部公共类型,编译后消失,不占运行时体积)与 export default axios(默认导出的混合对象实例)。导出设计的三条原则已在第 03 篇第 1 节展开,此处不重复。该文件没有任何逻辑,存在的意义是给 rollup.config.mjs 两个构建任务提供统一的 input: 'src/index.ts'。
职责:实例工厂与静态 API 挂载——把 Axios 类"拍平"为既是函数、又带全套方法与静态成员的混合对象。
导出清单:
| 导出 | 形态 | 说明 |
|---|---|---|
default | axios(AxiosStatic) | createInstance(defaults) 的产物 + 12 项静态挂载 |
逐成员走读:
VERSION(第 15 行):字符串常量 '0.2.1',手工维护,与 package.json 的同步由 scripts/smoke-dist.cjs 在发布前断言(见《06-部署运行》第 4 节)。createInstance(config)(第 18~23 行):三步——new Axios(config) 得到完整实例 context;Axios.prototype.request.bind(context) 得到绑定 this 的裸函数(这就是"函数即实例"的函数本体);extend(instance, context) 把实例属性与原型方法全部拷到函数上。注意绑定的只是 request:实例上的 get/post 等方法是靠 extend 拷贝上去的属性,而非绑定方法。这意味着拷贝的方法内部 this 指向——extend 拷的是函数引用,调用 axios.get(...) 时 this 是 axios 函数对象本身,而该对象上存在 defaults 与 interceptors(都是拷来的),所以 request 内部 this.defaults 依然能取到正确值。这是一个依赖"拷贝的完整性"的隐式契约:若未来 extend 漏拷了 defaults,所有方法族都会静默失效。axios.create(第 28~30 行):createInstance(mergeConfig(defaults, config))——总是以全局 defaults 为底再叠加传入配置,因此 axios.create() 不传参也拿到完整默认配置的独立实例。CancelToken、Cancel、isCancel、all(即 Promise.all)、spread(数组展开回调)、Axios(类本身)、AxiosHeaders、VERSION、HttpStatusCode、mergeConfig、getAdapter、isAxiosError、CanceledError、AxiosError、toFormData、formToJSON。这 16 项与 smoke-dist.cjs 的导出断言一一对应。依赖:./types(仅类型)、./core/Axios、./helpers/util(extend)、./defaults、./core/mergeConfig、./cancel/CancelToken、./cancel/Cancel、./core/AxiosHeaders、./cancel/CanceledError、./core/getAdapter、./helpers/HttpStatusCode、./helpers/error、./helpers/formData。它是全库唯一同时接触这些模块的文件,处于依赖图的顶端。
职责:库级默认配置与默认转换器。模块级单例,所有未显式 create 的请求共享它;axios.create 通过 mergeConfig 深合并产出副本而非引用,因此修改实例 defaults 不会污染全局单例(deepMergeStrat 的深拷贝语义,见第 02 篇第 2 节)。
导出清单:
| 导出 | 形态 | 说明 |
|---|---|---|
default | defaults(AxiosRequestConfig) | 含 transformRequest/transformResponse 两个默认转换器数组与 transitional 三项 |
| (模块内私有) | getContentType(headers) | 大小写不敏感读取 Content-Type |
逐函数走读:
getContentType(第 8~14 行):Object.keys(headers).find(k => k.toLowerCase() === 'content-type')。存在的原因是默认转换器必须在拍平前的分层 headers 上判断 multipart 意图,此时键名大小写不受控(用户可能写 content-type 小写顶层键)。transformRequest[0](第 37~55 行):默认请求转换器。判定序:①getContentType(headers) 含 multipart/form-data 且 data 是普通对象且存在可用的 FormData 构造器 → toFormData(data, new EnvFormData()) 直接转 FormData(注意第二个参数传的是新建的实例而非 undefined,构造器优先取 defaults.env.FormData,回退全局 FormData);②否则走 processHeaders(headers, data) 补 Content-Type: application/json;charset=utf-8(仅普通对象且缺失时)再调 helpers/data.ts 的 transformRequest(流直通 → 普通对象 JSON 序列化 → 其余原样)。transformResponse[0](第 58~62 行):薄包装,直接转调 helpers/data.ts 的 transformResponse(字符串尝试 JSON.parse,失败静默保持字符串)。validateStatus(第 64~66 行):status >= 200 && status < 300,被 core/settle.ts 消费。delete/get/head/options 空对象,post/put/patch 预置 Content-Type: application/x-www-form-urlencoded。这个"保守默认值 + 转换器按需补 JSON 头"的两段式设计在《02-模块设计》第 10 节有完整论证。依赖:./types、./helpers/headers(processHeaders)、./helpers/data、./helpers/formData(toFormData)、./helpers/util(isPlainObject)。
职责:请求编排。request 方法拼装动态 Promise 链,方法族是 request 的参数折算糖衣。
导出清单:
| 导出 | 形态 | 说明 |
|---|---|---|
default | class Axios | 数据属性仅 defaults、interceptors;方法 request、get、delete、head、options、post、put、patch、postForm、putForm、patchForm、getUri,私有 _requestMethodWithoutData、_requestMethodWithData、_requestForm |
| (模块内) | interface Interceptors、interface PromiseChain<T> | 链环形状 |
逐函数走读:
constructor(initConfig)(第 29~35 行):存储 defaults,创建请求/响应两个 InterceptorManager。每个实例的拦截器容器相互独立,这是 axios.create 隔离语义的物理基础。request(url, config?)(第 41config.url = url 是对调用方对象的就地写入。②合并归一(第 51mergeConfig(this.defaults, config) 产出无原型对象,随后 config.method.toLowerCase()(这里没有判空,依赖 defaults 保证 method: 'get' 存在——若用户以 mergeConfig 产物之外的方式传入无 method 的配置会抛 TypeError)。③拦截器收集(第 54runWhen(config) 过滤(返回 false 连链都不进),响应拦截器原样收集。④链构造(第 70syncRequest = requestInterceptors.every(i => i.options?.synchronous === true)——全真时走同步快路径(倒序逐个调用 resolved(conf),最后一次 Promise.resolve(conf).then(dispatchRequest));否则构造 chain 数组(中坚 dispatchRequest 居中,请求拦截器逐个 unshift),while (chain.length) 依次 promise.then(resolved, rejected)。请求链环的 rejected 位是 undefined,因此请求拦截器阶段的错误会跳过 dispatchRequest 直达响应拦截器的 rejected 钩子。⑤响应拦截器追加(第 100then,返回最终 promise。get/delete/head/options 委托 _requestMethodWithoutData,post/put/patch 委托 _requestMethodWithData,二者都是 Object.assign(config || {}, {...}) 折算后转调 request——会就地改写调用方传入的 config 对象(写 method/url/data)。postForm/putForm/patchForm 委托 _requestForm。_requestForm(第 182Object.assign({ 'Content-Type': 'multipart/form-data' }, config && config.headers) 前置 multipart 头(用户显式传入的同名头优先级更高);toFormData(data, undefined, config && config.formSerializer) 预转换请求体。注意第二参传 undefined,helpers/formData.ts 内部回退 new FormData() 全局构造器——没有走 defaults.env.FormData,与默认转换器(defaults.ts 第 42env.FormData)的路径不一致,已在第 03 篇标注 ⚠️ 待确认是否有意。getUri(第 147~150 行):只执行 mergeConfig + transformURL,不触发 data/headers 加工,用于预览最终地址。request(直接 return dispatchRequest(config))是刻意保留的演进线索。依赖:../types、./dispatchRequest(含具名 transformURL)、./InterceptorManager、./mergeConfig、../helpers/formData。
职责:Promise 链的中心环节——取消预检、配置三连加工、调适配器、响应转换。它是"横切关注点收敛到链路中层"这一架构决策(第 01 篇第 2 节)的物化。
导出清单:
| 导出 | 形态 | 说明 |
|---|---|---|
default | dispatchRequest(config) | 链中坚环节 |
processConfig | 具名导出 | url/data/headers 三连加工 |
transformURL | 具名导出 | baseURL 拼接 + params 序列化(被 Axios.getUri 复用) |
| (模块内私有) | transformResponseData、throwIfCancellationRequested | 响应转换与取消预检 |
逐函数走读:
dispatchRequest(config)(第 8throwIfCancellationRequested(config)(第 50cancelToken.throwIfRequested() 同步抛 Cancel 实例;signal.aborted 则抛 new CanceledError('canceled', config)——覆盖"取消发生在发请求之前"的时序),再 processConfig(config),最后 getAdapter(config)(config).then(成功转换, 失败转换)。失败分支对携带 response 的错误先 transformResponseData(e.response) 再继续 reject,保证 4xx/5xx 时 error.response.data 已解析。processConfig(第 24~28 行):顺序刻意为 url → data → headers。url 必须最先(默认转换器不关心 url);data 在 headers 拍平之前转换,因为默认请求转换器要按原始分层 headers 判断 Content-Type 并补写头;headers 拍平必须最后。transformURL(第 30~37 行):baseURL 存在且(allowAbsoluteUrls === undefined || allowAbsoluteUrls)且 url 非绝对地址时 combineURL,随后 buildURL 序列化 params。transformResponseData(第 39~48 行):transitional.forcedJSONParsing === false 时传给 transform 的转换器数组为 undefined,core/transform.ts 对空 fns 原样返回——这是 transitional 三项中第二个被消费的项(第一个是 clarifyTimeoutError)。依赖:../types、./getAdapter、../helpers/url、../helpers/headers、./transform、../cancel/CanceledError。
职责:策略模式的字段级配置合并。机制与策略表已在《02-模块设计》第 2 节完整展开,此处只补本篇视角的三点实现细节:其一,策略表用 Object.create(null) 创建(第 4 行),查找 strats[key] || defaultStrat 时不会误命中 Object.prototype 上的键;其二,结果对象同样无原型(第 53 行),因此调用方无法通过配置对象的 constructor 等原型键做任何原型链把戏;其三,第二轮遍历(第 60~64 行)只合并 config2 中值为 undefined 的键——注释强调"已显式给出(含 falsy 值)的字段不再重复合并",即 config2.timeout = 0 会阻止 defaults 的 timeout 参与,但 fromVal2Strat 下 config2.data = null 会以 null 为准而非继承,两类策略对 falsy 的语义都是"显式即生效"。
导出清单:default mergeConfig(config1, config2?);模块内私有 defaultStrat、fromVal2Strat、deepMergeStrat 与三个策略键数组。
依赖:../types、../helpers/util(deepMerge、isPlainObject)。
职责:拦截器的有序存取容器(稀疏数组)。use push 后返回下标;eject 把槽位置 null 而非 splice(id 永不错位);forEach 跳过空槽。synchronous/runWhen 选项接口 InterceptorOptions<T> 定义在此文件(第 3~6 行)并导出,但未进入 src/types/index.ts 的公共类型面(第 03 篇差异清单第 3.4 节)。泛型参数 T 使同一个类同时服务请求侧(AxiosRequestConfig)与响应侧(AxiosResponse)。
导出清单:default class InterceptorManager<T>(方法 use/forEach/eject);具名 interface InterceptorOptions<T>。
依赖:仅 ../types(ResolvedFn/RejectedFn)。
全库最小的模块。transform(data, headers, fns?):fns 为空原样返回;单函数包装成数组;forEach 串行执行 data = fn(data, headers)。它是"转换器按数组顺序串行管道执行"语义的唯一实现点,dispatchRequest 的请求侧与响应侧共用。无状态、无依赖(除类型),18 行里没有任何环境探测。
职责:适配器注册表与解析。
逐函数走读:模块顶部的 knownAdapters(第 1014 行)在模块加载时求值:55 行)。xhr: typeof XMLHttpRequest !== 'undefined' ? xhrAdapter : null——浏览器/jsdom 里注册 xhr 适配器函数,其他环境注册 null;http 无条件注册 httpAdapter(其内部对 Node 内置模块的依赖由 rollup 的 external/shim 机制处理);fetch 无条件注册 fetchAdapter(运行时无全局 fetch 时以 ERR_NOT_SUPPORT 拒绝,fetch.ts 第 46getAdapter(config)(第 16~53 行)把 config.adapter 归一为数组:null → 默认 ['xhr', 'http', 'fetch'];字符串/函数/其他非数组 → 单元素数组;随后线性扫描——函数直接返回,字符串查表,查不到(或表值为 null)静默尝试下一个;全部失败抛普通 Error(注意不是 AxiosError),错误消息里用 knownAdapters[k] 过滤后列出当前环境真正可用的适配器名。
依赖:../types、./adapters/xhr、./adapters/http、./adapters/fetch。它是三适配器唯一的汇聚点。
三适配器共享的最终裁判,机制见第 02 篇第 5 节。本篇补充判定式的精确语义:if (!response.status || !validateStatus || validateStatus(response.status))——三个放行条件分别是"状态码为 0"(xhr 跨域失败等场景不会走到这里,因为 xhr 适配器已把 status 0 静默忽略;此条件主要服务 Node 端自定义 transport 的边缘形态)、"未配置 validateStatus"(任意状态码都算成功)、"validateStatus 放行"。拒绝分支的错误码以 500 为界(>= 500 → ERR_BAD_RESPONSE,否则 ERR_BAD_REQUEST),并通过 createError 的第 6 参把数值状态写入 error.status。test/core/settle.spec.ts 的注释记录了一次历史回归——"http 适配器此前反转"过 4xx/5xx 错误码,这正是把判定收敛到一份实现的价值:修一处即修三端。
依赖:../types、../helpers/error。
职责:以规范化(小写)键名存储首部的容器类。机制与官方方法子集的取舍见《02-模块设计》第 7 节。本篇补充实现要点:类本体就是存储容器(索引签名 [key: string]: any),没有独立的内部 Map;构造器三分支(普通对象逐键 set、AxiosHeaders 实例先 toJSON 再逐键、原始报文按 \n 行切首个 :);set 的 rewrite 参数默认 true,值为 undefined/null 等价删除;has 是"存在即真"(typeof !== 'undefined'),空字符串也算有;merge 对已有数组值做 concat 而非覆盖;normalize 把所有非小写键重写为小写。静态方法 from(等价 new)与 concat(多源合并)。请求链路中它只在两个位置出现:flattenHeaders 兼容用户传入实例(先 toJSON),以及用户在拦截器里可以选择用它操作 headers;库内部流转仍以普通对象为主。
依赖:无 import(自包含,连 util.ts 的 isPlainObject 都在文件内重新实现了一份)——这是一个刻意的解耦决定,使 AxiosHeaders 可以被单独抽取使用。
三个适配器共用签名 (config: AxiosRequestConfig) => AxiosPromise,共同出口是 settle。横切对照表(取消/超时/进度/headers/认证/XSRF/特有配置)见《02-模块设计》第 5 节,本篇按文件展开内部函数。
职责:浏览器/jsdom 的 XMLHttpRequest 适配器,全库最短的适配器,适合作为"适配器如何写"的范本。
结构:单个导出函数包裹一个大 Promise,executor 内先解构 config(第 1227 行,49 行)。headers = {}、data = null、method = 'get' 均有默认值兜底),随后按固定装配序执行 request.open → configureRequest → addEvents → processHeaders → processCancel → processSignal → request.send(data)(第 41
逐函数走读:
cleanup(第 34~39 行):闭包变量 removeAbortListener 的幂等清理器,所有 settle 出口(handleResponse、onerror、ontimeout)调用。configureRequest(第 51~61 行):responseType !== 'stream' 才设置(xhr 不支持 stream,显式跳过);timeout 与 withCredentials 直传。addEvents(第 63onreadystatechange 在 readyState === 4 且 status !== 0 时组装 AxiosResponse(headers 经 parseHeaders 小写化,data 按 responseType 取 response 或 responseText)并交给 handleResponse → settle;status === 0 静默 return(网络层失败留给 onerror)。onerror 以 ERR_NETWORK reject;ontimeout 按 transitional.clarifyTimeoutError 选 ETIMEDOUT/ECONNABORTED。makeProgressHandler(第 106lastLoaded 计算增量 bytes,产出带 loaded/total/progress/bytes/lengthComputable/event 与方向标记的规范化事件,分别挂到 request.onprogress 与 request.upload.onprogress。processHeaders(第 135~161 行):四步——FormData 时 delete headers['Content-Type'];XSRF 判定(withXSRFToken 为函数则调用其返回值,否则 !== false,叠加 withCredentials || isURLSameOrigin(url) 与 xsrfCookieName 存在性,从 cookie.read 读值写入 headers[xsrfHeaderName]);auth 时写 Authorization: Basic btoa(...);最后遍历 headers——data === null 时删 content-type,其余逐个 setRequestHeader。注意解构出的 headers 与 config.headers 是同一对象引用,本函数的全部删除/写入都会反映到最终 response.config.headers(与 http/fetch 的浅拷贝不一致,详见《12-FAQ》《13-技术债务》)。processCancel(第 163~177 行):订阅 cancelToken.promise,触发时 request.abort() 并以 Cancel 实例 reject;.catch(() => {}) 兜底防止 promise 链上未处理的 rejection。processSignal(第 179~198 行):signal.aborted 已真则立即 abort+reject;否则挂 abort 监听(触发时同样 abort+reject CanceledError),并把移除函数存入 removeAbortListener 供 cleanup 调用。依赖:../../types、../../helpers/headers、../../helpers/error、../settle、../../helpers/url、../../helpers/cookie、../../helpers/util、../../cancel/CanceledError。
职责:Node http/https 适配器,全库最重文件——重定向、代理(含 HTTPS CONNECT 隧道)、解压、内容长度护栏、流式响应、上传进度与限速全部在此。内部结构是"一个导出函数 + 一个自递归的 _send + 两个每跳内闭包(onResponse/finalize)"。
模块级私有函数:
| 函数 | 位置 | 机制 |
|---|---|---|
parseRawHeaders | 第 14~20 行 | 直接把 res.rawHeaders 数组两两配对小写化,跳过字符串序列化/再解析(比 parseHeaders 省一趟) |
toNodeFormData | 第 31~53 行 | 把 Web FormData 逐键搬运到 form-data 实例;值为带 stream.pipe 的对象(Web File/Blob 兼容形态)时按文件附加(filename/knownLength)。NodeFormData 未安装时返回 null |
isStream | 第 55~57 行 | typeof data.pipe === 'function' |
buildDecompressStream | 第 59~76 行 | 按 content-encoding 挂 gzip/deflate/br 解压流;decompress !== false 且命中才包 |
| 模块顶部 try/catch | 第 24~29 行 | require('form-data') 可选依赖探测,失败置 null(降级为字符串发送) |
httpAdapter 主体(第 78115 行):创建 Promise,先建立 107 行注释记录了从"每跳注册"改为"只注册一次"的历史修复)。abortCleanups 数组与包装后的 resolve/reject(两者都先执行 runAbortCleanups 再落定——重定向会多次注册 signal 监听,这里统一在最终 settle 时移除);url 缺失即 ERR_INVALID_URL;resolveMaxRates(config.maxRate) 拆上行/下行限速;cancelToken.promise.then 只注册一次,回调里 currentReq.abort()——currentReq 变量每跳 finalize 时被更新,因此取消的总是"当前跳"(第 105
_send(currentUrl, redirectCount, methodOverride?, dataOverride?, stripAuth?)(第 117~547 行)逐段:
new URL 失败 → ERR_INVALID_URL。config.transport 可整体替换,否则按协议选 https/http。{ ...config.headers } 浅拷贝(与 xhr 的就地修改形成三端差异点);stripAuth 时删除 authorization/proxy-authorization/cookie 三键(跨 host 凭据剥离,对齐 follow-redirects)。agent 按 http/https 分别取 config.httpAgent/httpsAgent;auth(未 stripAuth 时拼 user:pass 交给 Node 底层);timeout/socketPath/family/lookup/insecureHTTPParser 透传。onResponse(第 171parseRawHeaders;重定向判定(第 176redirectCount >= maxRedirects,默认 5)→ ERR_FR_TOO_MANY_REDIRECTS;方法改写(301/302 且方法为 post,或 303 且方法非 get/head → 改写 GET、丢体、删全部 content-* 头);beforeRedirect(redirectOptions, {headers, statusCode}) 抛错即中断、可改写 url;跨 host(new URL(nextUrl).host !== parsed.host)置 stripAuth || crossHost 递归 _send。非重定向:组装 response(data 先占位 undefined);若配置了下载进度或下行限速,在解压前给原始响应流包 createProgressStream(total 取 content-length);responseType === 'stream' 时直接把流挂到 response.data 并 settle;否则解压后 'data'/'end'/'error' 三监听——data 累积 chunks 且实时校验 maxContentLength(超限 stream.destroy() 并 reject,message 为 maxContentLength exceeded、code 为 null),end 时按 responseType 组装 Buffer 或按 responseEncoding(默认 utf8)解码字符串,error 时 reject(code 亦为 null)。finalize(第 295form-data 可用时先删除任意大小写的 content-type(第 305Object.assign(headers, nodeForm.getHeaders()),body 转为 pipeSource;form-data 不可用降级为字符串;isStream 时 body 即 pipeSource;其余先校验 maxBodyLength(超限 ERR_BAD_REQUEST)再作为 writeBody。②创建请求:transport.request(options, onResponse),挂 error 监听(clientReq.aborted 时跳过——abort 引起的错误不重复报)与 setTimeout 超时(同 xhr 的错误码开关逻辑),挂 signal 监听(已 aborted 立即 reject;否则监听并把移除函数压入 abortCleanups)。③④发送:pipeSource 路径可叠加两个 Transform——流式 maxBodyLength 计数器(边发边计数,超限 abort+reject+callback(error),且 counter 的 error 被吞掉避免未处理异常)与上行进度/限速流,最终 pipe(clientReq);writeBody 路径若无上行跟踪则直接 write + end,有则包成一次性 Readable 过限速流再 pipe;无体直接 end()。config.proxy === false 显式禁用;否则 config.proxy || getProxyForUrl(currentUrl)。HTTPS 目标走 CONNECT 隧道——向代理发 CONNECT host:port,connect 事件非 200 → ERR_PROXY(自增码,见《13-技术债务》);成功则把 socket 挂到 options.socket 并设 servername,显式从 httpsAgent.options 继承 rejectUnauthorized/ca/cert/key/pfx/passphrase/ciphers/secureProtocol(自定义 socket 时 Node 不会自动应用 agent 的 TLS 配置);HTTP 目标则把 hostname/port 改指代理、path 用绝对 URL、写入 Proxy-Authorization。无代理直接 finalize()。依赖:../../types、../../helpers/error、../settle、../../helpers/util、../../helpers/progressStream、../../helpers/proxyFromEnv、../../cancel/CanceledError、Node 内置 http/https/zlib/stream、可选 form-data。
职责:基于全局 fetch 的适配器,面向 Service Worker / Cloudflare Workers 等无 XHR、无 Node http 的运行时。
模块级私有函数:base64(第 1015 行,btoa 优先,Node 回退 Buffer)、26 行,单次分配合并 Uint8Array 分片)、concatChunks(第 18decodeText(第 29~34 行,TextDecoder 优先,Node 回退 Buffer)。
主体走读:
typeof fetch === 'undefined' → ERR_NOT_SUPPORT reject。注册表在构建期稳定、环境差异推迟到运行时表达的决策(第 01 篇 3.2 节)在此落地。AbortController(环境缺失时 controller 为 null,abortFetch 变为无操作)统一承载三种中断——config.signal(已 aborted 立即 reject;否则监听)、cancelToken.promise(触发时以 Cancel 实例 reject)、timeout 定时器(触发时按 clarifyTimeoutError 定码 reject)。三者都置 settledEarly = true 并 cleanup()(清 timer + 移除 signal 监听)+ abortFetch()。requestHeaders = { ...headers } 浅拷贝;无体或 FormData 时删 content-type;auth 写 Authorization;XSRF 判定与 xhr 相同,额外要求 typeof document !== 'undefined';init 先展开 config.fetchOptions 再覆写 method/headers,随后按需补 body/signal/credentials: 'include'——因为核心字段在后位,fetchOptions 无法覆盖它们。fetchRes.headers.forEach 小写化收集;emitProgress 闭包(无回调时短路);finish(body) 组装 response(request: null 恒定)并 settle;onBodyError 统一按 ERR_BAD_RESPONSE。下载进度路径(canTrack,第 246body.getReader() 的for 循环增量读取(第 251 行注释说明用循环替代递归 pump 以避免长响应下的 microtask 链),读完 concatChunks 后按 responseType 组装(arraybuffer → slice 出精确 buffer;blob → new Blob([merged]);其余 → decodeText)。无进度需求时按 responseType 走 arrayBuffer()/blob()/text(),responseType === 'stream' 直接把原始 ReadableStream 交给 finish。fetch 的 reject 回调(第 301~314 行)先 cleanup,settledEarly 为真则静默吞掉 AbortError 连带失败,否则 ERR_NETWORK。onUploadProgress 被解构忽略——第 03 篇差异清单第 4 条。依赖:../../types、../../helpers/error、../../helpers/util、../../helpers/url、../../helpers/cookie、../settle、../../cancel/CanceledError。零 Node 依赖。
机制已在《02-模块设计》第 6.1 节给出完整代码。本篇补充实现视角:核心是把"取消"建模为一个手工 resolve 的 Promise<Cancel>——constructor 里 executor(cancel) 立即执行,cancel 闭包先判 this.reason 幂等(重复取消只生效一次)再赋值并 resolve 内部 promise。throwIfRequested 是 reason 存在时同步 throw,被 dispatchRequest 预检消费;static source() 是"executor 持有 cancel 函数"样板代码的糖。注意 promise 是公开属性,任何持有 token 的一方都能订阅——CancelToken 的发布订阅完全复用 Promise 的多订阅语义,没有自建监听器数组。依赖:../types、./Cancel。
旧版取消原因类,仅 message 一个字段。真正重要的是同文件的具名导出 isCancel(value):value instanceof Cancel || value instanceof CanceledError——双轨取消的统一判定收口在这 2 行。依赖:./CanceledError(注意取消组内部存在 Cancel → CanceledError 的反向小依赖,CanceledError 又依赖 helpers/error,链条为 Cancel → CanceledError → AxiosError)。
继承 AxiosError,构造时固定 code: 'ERR_CANCELED'、config || {} 兜底、name = 'CanceledError',并显式 Object.setPrototypeOf(this, CanceledError.prototype)——原因与 helpers/error.ts 的构造器内 setPrototypeOf 互为表里:父类构造先把原型设为 AxiosError.prototype(修 ES5 丢原型问题),子类再覆写回自己的原型,保证 instanceof CanceledError 正确。依赖:../helpers/error、../types。
导出 isDate/isPlainObject/isURLSearchParams(均基于 Object.prototype.toString)、extend、deepMerge、isFormData。机制重点两处:extend(第 2138 行)除 62 行)以 for...in 外沿原型链 getOwnPropertyNames 拷贝并跳过 constructor 与已存在键——注释明确记录"ES6 class 方法是 non-enumerable,Vite/esbuild 保留原生 class 语义时 for...in 会漏拷全部方法"这一坑;deepMerge(第 40Object.create(null) 为容器递归合并普通对象,数组与原始值直接覆盖。isFormData 以 typeof FormData !== 'undefined' 前置守卫,非浏览器环境安全。依赖:无。
导出 buildURL/isAbsoluteURL/combineURL/isURLSameOrigin。buildURL(第 1685 行)是 params 序列化的唯一实现:先从 13 行);最后剥离 paramsSerializer 的函数/对象形态中解析出 serializeFn/encodeFn/indexes 三要素;有 serializeFn 交给用户;params 本身是 URLSearchParams 则 toString();否则逐键遍历——null/undefined 跳过,数组按 indexes 三态(true → foo[i],null → foo,缺省 → foo[])生成键名,Date → ISO 字符串、普通对象 → JSON,encodeFn 默认是 encodeURIComponent 加白名单还原(@ : $ , 空格→+ [ ],第 4# 后按 ?/& 拼接。isURLSameOrigin(第 104~111 行)浏览器用 <a> 标签解析 origin(urlParsingNode/currentOrigin 两个模块级缓存 + ensureNode 惰性初始化),非浏览器恒返回 true——这个"Node 视为同源"的默认值使 XSRF 判定在 http 适配器语境下不会误伤(http 适配器本来就不做 XSRF)。安全维度的分析见《08-安全与权限设计》。依赖:./util、../types。
导出 processHeaders/parseHeaders/flattenHeaders。processHeaders 先 normalizeHeaderName 把大小写变体统一成 Content-Type,再在普通对象 data 缺 Content-Type 时补 application/json;charset=utf-8。parseHeaders 把 XHR 原始头文本按 \r\n 行、首个 : 切分,键 trim+小写(注释说明值中可能含冒号如时间字段)。flattenHeaders(第 64~81 行)先兼容 AxiosHeaders 实例(toJSON),再 deepMerge(headers.common, headers[method], headers),最后删除 common 与 7 个方法键。依赖:./util、../types、../core/AxiosHeaders(仅 instanceof 判断——注意 helpers 反向依赖了 core,这是依赖图上唯一一条 helpers → core 边,读图时不要漏)。
默认转换器的实体。transformRequest:先放行 data.pipe === 'function' 的 Node 可读流(注释说明流的 toString 标签也是 [object Object],会被 isPlainObject 误判而遭 JSON.stringify 破坏),普通对象 JSON 序列化,其余原样。transformResponse:仅字符串尝试 JSON.parse,失败静默。两函数合计 24 行,却是全库数据处理行为的事实标准。依赖:./util。
导出 AxiosErrorCodes(13 个错误码常量表)、class AxiosError、createError、isAxiosError。AxiosError 构造器里 Object.setPrototypeOf(this, AxiosError.prototype) 修 ES5 下 class extends Error 丢原型的问题(注释详述);status 初始为 null,toJSON 序列化 message/name/code/status/config/response 六项。createError 是全库唯一的错误对象工厂,第 6 参 status 缺省时不覆盖。isAxiosError 用 instanceof 而非布尔属性判定(布尔属性可伪造)。AxiosErrorCodes 中 ERR_BAD_OPTION_VALUE/ERR_BAD_OPTION/ERR_DEPRECATED 三个码声明未使用(对齐官方码表的占位),ERR_PROXY 是本项目自增码——两者都是《13-技术债务》的条目。依赖:../types。
导出 default cookie = { read(name) }:正则 (^|;\s*)(name)=([^;]*) 匹配 document.cookie,命中则 decodeURIComponent 返回。正则直接内插 name 未转义——name 来自 config.xsrfCookieName,通常是常量,风险可忽略但非零(见《08-安全与权限设计》)。依赖:无。
导出 toFormData/formToJSON。toFormData(obj, form?, options?):form 缺省 new FormData();obj 本身是 FormData 则逐键搬运返回;否则对每个顶层键 walk 递归——visitor 可自定义(返回 false 剪枝),默认 visitor 走 defaultVisitor(undefined/null → 空串、Date → ISO、数组逐项 append、其余原样);路径生成规则:数组 indexes === false 无下标、true 数字下标、缺省空括号,对象 dots ? a.b : a[b]。formToJSON 用 parseKey(正则提取路径段)+ assignDeep 反解——注释强调用 hasOwnProperty 而非 in(后者会沿原型链命中 constructor 等),数值段还原数组、重复键聚合为数组。FormSerializerOptions 中的 metaTokens/maxDepth 在此文件无消费点(第 13 篇条目)。依赖:./util、../types。
导出 createProgressStream/resolveMaxRates 与接口 ProgressStreamOptions。createProgressStream 返回 Transform:每个 chunk 统计累计字节,2 秒滑动窗口(samples 数组,头部 shift 丢弃窗口外采样)计算瞬时 rate,事件携带 loaded/total/progress/bytes/rate/estimated/lengthComputable 与方向标记;maxRate 按"累计字节/期望速率 - 实际耗时"计算延迟,超前则 setTimeout(callback, delay) 放行实现平均限速。resolveMaxRates 把 number | [number, number] 归一为 [上行, 下行],0 视为不限速。依赖:stream(Node 内置)、../types。
导出 getProxyForUrl(targetUrl)。内部三段:getEnv(大小写兼容读 process.env,无 process 时安全返回 undefined);splitHostPort(IPv6 方括号/多冒号兼容的 NO_PROXY 条目拆分);isNoProxy(* 全排除、.后缀 匹配、精确主机、带端口条目)。主函数解析目标 URL,按协议取 https_proxy || all_proxy 或 http_proxy || all_proxy,解析代理 URL 得到 AxiosProxyConfig(host 剥 IPv6 方括号、port 缺省按协议 443/80、auth 从 URL 的 username/password decode)。注释明确"对齐官方 axios(proxy-from-env)的行为"。依赖:../types。
状态码常量表(1xx~5xx 全量 63 项),default 导出对象、具名导出同值类型。唯一的"纯数据"文件,被挂到 axios.HttpStatusCode。
unavailable(module) 工厂产出"调用即抛中文错误"的占位函数,具名导出 request(http)、createGunzip/createInflate/createBrotliDecompress(zlib)、Readable/Transform(stream),default 导出(form-data)。浏览器构建时 rollup.config.mjs 的 alias 把这五个模块名全部映射到本文件,UMD 产物因此零 external。维护契约:http 适配器若引入新的内置 API,本文件必须同步补齐导出,否则浏览器构建要到运行时才暴露缺符号(第 01 篇 3.7 节)。jest 覆盖率统计显式排除本目录(占位代码无可测分支)。
全库唯一"只有声明没有实现"的文件。文件顶部用 import type 引入五个实现模块的类型(AxiosHeaders、CanceledError、getAdapter、mergeConfig、HttpStatusCode、isAxiosError/AxiosErrorClass)——全部是 type-only,保证类型面无运行时代价。结构分七段:Method 与 AxiosRequestConfig(第 886 行,末尾宽松索引签名)、辅助配置接口(ProgressEvent/ParamsSerializer/FormSerializer/Transitional/ProxyConfig,第 90147 行)、响应三件套(AxiosResponse/AxiosPromise/AxiosAdapter,第 150163 行)、错误 interface(第 166173 行)、实例四层类型(Axios/AxiosInstance/AxiosClassStatic/AxiosStatic,第 175250 行)、拦截器三类型(第 252268 行)、取消七类型与凭据(第 270~307 行)。取舍细节(interface/class 双轨、AxiosPromise 空接口、四层静态类型拆分、宽松索引签名的代价)见《02-模块设计》第 11 节;全字段速查表见《15-接口全量参考》。
下表汇总"谁依赖谁"(仅运行时 import,类型 import 不计),可作为重构影响面分析的起点:
| 文件 | 直接依赖(运行时) |
|---|---|
src/axios.ts | core/Axios、helpers/util、defaults、core/mergeConfig、cancel/CancelToken、cancel/Cancel、core/AxiosHeaders、cancel/CanceledError、core/getAdapter、helpers/HttpStatusCode、helpers/error、helpers/formData |
src/defaults.ts | helpers/headers、helpers/data、helpers/formData、helpers/util |
src/core/Axios.ts | core/dispatchRequest、core/InterceptorManager、core/mergeConfig、helpers/formData |
src/core/dispatchRequest.ts | core/getAdapter、helpers/url、helpers/headers、core/transform、cancel/CanceledError |
src/core/mergeConfig.ts | helpers/util |
src/core/getAdapter.ts | adapters/xhr、adapters/http、adapters/fetch |
src/core/settle.ts | helpers/error |
src/core/adapters/xhr.ts | helpers/headers、helpers/error、core/settle、helpers/url、helpers/cookie、helpers/util、cancel/CanceledError |
src/core/adapters/http.ts | helpers/error、core/settle、helpers/util、helpers/progressStream、helpers/proxyFromEnv、cancel/CanceledError、http/https/zlib/stream、form-data(可选) |
src/core/adapters/fetch.ts | helpers/error、helpers/util、helpers/url、helpers/cookie、core/settle、cancel/CanceledError |
src/cancel/Cancel.ts | cancel/CanceledError |
src/cancel/CanceledError.ts | helpers/error |
src/helpers/headers.ts | helpers/util、core/AxiosHeaders(唯一 helpers → core 边) |
src/helpers/url.ts | helpers/util |
src/helpers/formData.ts | helpers/util |
src/helpers/progressStream.ts | stream |
src/helpers/proxyFromEnv.ts | (无) |
src/helpers/cookie.ts / data.ts / error.ts / HttpStatusCode.ts / util.ts | (无或仅类型) |
三条结构性结论收束全篇:第一,core/Axios.ts 不 import axios.ts,依赖单向向下,axios.create 的所有实例共享同一套 core;第二,helpers/headers.ts → core/AxiosHeaders 是工具层唯一的例外边(instanceof 需要),若想彻底解耦可改为鸭子判断;第三,cancel/Cancel.ts → CanceledError → helpers/error 构成取消组对错误组的单向依赖,isCancel 因此能同时识别两轨——这三条是新增代码时判断"我的 import 是否破坏架构"的最快参照。