Axios Promise 机制实战:从 then/catch/finally 到 async/await 与并发请求的完整解析
2026/9/7 8:26:59 网站建设 项目流程

Axios Promise 机制实战:从 then/catch/finally 到 async/await 与并发请求的完整解析

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

Axios 的每一个请求方法都返回一个标准的 ES6 Promise:成功时 resolve 为AxiosResponse响应对象,失败时 reject 为一个AxiosError。本文基于官方文档 Promise 指南,结合当前仓库源码,完整讲解 Promise 的 resolve/reject 判定逻辑、AxiosPromise<T, D, P>类型设计、.then()/.catch()/.finally()async/await两种消费方式,以及Promise.all/Promise.allSettled并发请求与请求链式编排,帮助你把异步请求代码写得既健壮又类型安全。

axios 为什么返回标准 Promise

Axios 没有自定义任何 Promise 替代品,axios.get()axios.post()等方法的返回值就是原生的Promise实例,因此可以无缝使用浏览器与 Node.js 内置的全部 Promise API。

从源码结构看,这条 Promise 链路的核心入口是 lib/core/Axios.js 中的request方法。它是async函数,内部调用_request()完成配置合并与拦截器编排:

// lib/core/Axios.js async request(configOrUrl, config) { try { return await this._request(configOrUrl, config); } catch (err) { // 对 reject 出来的 Error 补充 stack 信息后重新 throw ... throw err; } }

_request()在没有任何异步请求拦截器时会走同步快路径;只要存在异步拦截器,就会把“请求拦截器 → dispatchRequest → 响应拦截器”拼成一条完整的 Promise 链:

// lib/core/Axios.js(_request 内部,简化) if (!synchronousRequestInterceptors) { const chain = [dispatchRequest.bind(this), undefined]; chain.unshift(...requestInterceptorChain); chain.push(...responseInterceptorChain); len = chain.length; promise = Promise.resolve(config); while (i < len) { promise = promise.then(chain[i++], chain[i++]); } return promise; }

可以看到整个请求管道就是一连串promise.then(...)调用,这也是为什么拦截器中可以返回 Promise 或async函数——axios 会像对待标准 thenable 一样等待其完成。

真正发出网络请求的是 lib/core/dispatchRequest.js。它先做取消检查、请求数据转换,然后通过 adapter 发出请求,并在 adapter 返回的 Promise 上挂接成功/失败两个分支:

// lib/core/dispatchRequest.js(简化) return adapter(config).then( function onAdapterResolution(response) { throwIfCancellationRequested(config); response.data = transformData.call(config, config.transformResponse, response); return response; }, function onAdapterRejection(reason) { // 非取消错误时同样执行 transformResponse,然后 Promise.reject(reason) return Promise.reject(reason); } );

值得注意的是:无论是成功分支还是失败分支,只要存在reason.response,axios 都会对它执行transformResponse。这意味着即使在.catch()里,error.response.data也是已经过 JSON 解析(或自定义 transform)后的结构化数据,而不是原始字符串。

resolve 还是 reject:settle 的判定逻辑

HTTP 2xx 一定 resolve、4xx/5xx 一定 reject 并不是默认成立的,判定逻辑集中在 lib/core/settle.js:

// lib/core/settle.js export default function settle(resolve, reject, response) { const validateStatus = response.config.validateStatus; if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { reject(new AxiosError( 'Request failed with status code ' + response.status, response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE, response.config, response.request, response )); } }

默认的validateStatus定义在 lib/defaults/index.js:

validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }

即只有 200–299 会进入.then(),其余状态码会被包装成AxiosError交给.catch()。reject 时AxiosError携带的code有两个关键区分:

  • 状态码在 400–499 之间:ERR_BAD_REQUEST
  • 其他状态码(如 5xx):ERR_BAD_RESPONSE

完整的错误常量列表(ECONNABORTEDETIMEDOUTERR_NETWORKERR_CANCELED等)定义在 lib/core/AxiosError.js。这些常量配合error.response?.status.catch()分支里做精细化错误处理的基础。相关的 settle 行为在 tests/unit/core/settle.test.js 中有对应测试覆盖。

AxiosError本身的字段(见 lib/core/AxiosError.js 构造函数)在 reject 时都会挂载:

this.name = 'AxiosError'; this.isAxiosError = true; code && (this.code = code); config && (this.config = config); request && (this.request = request); if (response) { this.response = response; this.status = response.status; }

因此在.catch()中你总能拿到error.config(可回溯请求参数)、error.request(底层请求对象)、error.response(当请求已经收到服务端响应时)以及error.status。判断一个 reject 是否为 axios 错误,标准做法是检查isAxiosError标记或使用 lib/helpers/isAxiosError.js 提供的isAxiosError()工具函数。

TypeScript 集成:AxiosPromise<T, D, P>

对于 TypeScript 项目,axios 的类型声明提供了AxiosPromise<T, D, P>这个专用别名,其定义在 index.d.ts:

export type AxiosPromise<T = any, D = any, P = any> = Promise<AxiosResponse<T, D, {}, P>>;

其中TDP三个泛型分别约束响应数据、请求体(data)与查询参数(params)。AxiosResponse<T, D, H, P>接口的完整字段如下(index.d.ts):

export interface AxiosResponse<T = any, D = any, H = {}, P = any> { data: T; status: number; statusText: string; headers: (H & RawAxiosResponseHeaders) | AxiosResponseHeaders; config: InternalAxiosRequestConfig<D, P>; request?: any; }

关键设计在于config: InternalAxiosRequestConfig<D, P>——请求的数据与 query 参数会被保留在response.config,因此在then回调里你不仅能拿到响应类型,还能拿回当初发出的请求类型。官方文档给出的示例:

declare const search: AxiosPromise<SearchResponse, RequestBody, SearchParams>; search.then((response) => { response.data; // SearchResponse response.config.data; // RequestBody | undefined response.config.params; // SearchParams | undefined });

这意味着在封装带类型的 API 层时(例如const user: AxiosPromise<User, CreateUserDto, UserQuery> = client.get(...)),下游代码消费response时可获得完整的端到端类型推导,无需手动断言。

then / catch / finally 处理结果

因为 axios 返回标准 Promise,可以直接使用.then().catch().finally()处理请求结果:

axios.get("/api/users") .then((response) => { console.log(response.data); }) .catch((error) => { console.error("Request failed:", error.message); }) .finally(() => { console.log("Request finished"); });

几个实用要点:

  • .then()回调收到的就是AxiosResponse对象,业务数据在response.data;状态码、状态文本、响应头分别在statusstatusTextheaders上。
  • .catch()回调收到的通常是AxiosError(网络层错误、超时、取消、非 2xx 状态码均走此分支),error.message会给出如Request failed with status code 404这样的可读信息。
  • .finally()无论 resolve 还是 reject 都会执行,适合用于关闭 loading 态、释放请求锁等收尾逻辑。

async / await:推荐写法

官方文档指出,对大多数代码库而言async/await是推荐写法,它让异步代码读起来像同步代码:

async function fetchUser(id) { try { const response = await axios.get(`/api/users/${id}`); return response.data; } catch (error) { console.error("Failed to fetch user:", error.message); throw error; } }

结合前文 settle 的判定逻辑,try/catch中捕获到的error具备完整的诊断字段。一个更贴近生产的错误处理骨架:

try { const { data } = await axios.get("/api/order/42"); } catch (error) { if (error.isAxiosError) { if (error.code === AxiosError.ECONNABORTED || error.code === AxiosError.ETIMEDOUT) { // 超时处理 } else if (error.response) { // 服务端已响应但状态码非 2xx:error.response.status 可区分 401/403/404/5xx } else if (error.code === AxiosError.ERR_NETWORK) { // 纯网络错误(请求未发出或被拦截) } } }

此外,lib/core/Axios.js 中的request方法在捕获到 reject 的Error后会尝试用Error.captureStackTrace补全堆栈信息再重新抛出,这样在await场景中打出的错误堆栈能定位到真实的业务调用位置,而不是 axios 内部实现。

并行请求:Promise.all 与 Promise.allSettled

由于 axios 返回标准 Promise,可以直接用Promise.all同时发起多个请求并等待它们全部完成:

const [users, posts] = await Promise.all([ axios.get("/api/users"), axios.get("/api/posts"), ]); console.log(users.data, posts.data);

需要特别注意:Promise.all在任意一个请求失败时会立即 reject。如果希望容忍部分失败、收集全部结果,应改用Promise.allSettled

const results = await Promise.allSettled([ axios.get("/api/users"), axios.get("/api/posts"), ]); results.forEach((result) => { if (result.status === "fulfilled") { console.log(result.value.data); } else { console.error("Request failed:", result.reason.message); } });

allSettled的每个结果元素带有status: "fulfilled" | "rejected",成功时数据在result.valueAxiosResponse),失败时错误在result.reasonAxiosError)。官方浏览器端 Promise 行为在 tests/browser/promise.browser.test.js 中有专门的回归测试,可验证请求确实符合标准 Promise 语义。

选择建议:

  • 多个请求之间存在“必须全部成功”的依赖语义(如页面初始化需要同时拿到用户信息与权限列表):用Promise.all,任一失败即整体失败。
  • 聚合型场景(如仪表盘同时拉取多个统计接口,部分接口挂掉不应拖垮整体):用Promise.allSettled,逐条检查status后降级展示。

链式请求:把上一个响应用给下一个

通过链式.then()调用,可以让请求顺序执行,并把上一请求的数据传给下一请求:

axios.get("/api/user/1") .then(({ data: user }) => axios.get(`/api/posts?userId=${user.id}`)) .then(({ data: posts }) => { console.log("Posts for user:", posts); }) .catch(console.error);

这里的关键是.then()回调返回了新的 axios 请求(即一个新的 Promise),Promise 链会自动等待其完成后再进入下一个.then()。整条链共用末尾的一个.catch(),因此任意一环失败(包括第二跳请求本身)都会被统一捕获。

这种链式语义不是巧合:回到lib/core/Axios.js的拦截器编排,axios 自身构造请求管道的方式正是promise = promise.then(chain[i++], chain[i++])的循环——拦截器返回 thenable 时链条会自然“挂起”等待,外部.then()链与内部拦截器链遵循完全相同的标准 Promise 组合规则。这也解释了为什么在请求拦截器里返回async函数(例如异步获取 token 后再放行请求)是完全受支持的。

环境要求:不支持 ES6 Promise 时

axios 构建在原生 ES6 Promise API 之上。如果你的运行环境不支持原生 Promise(较旧的浏览器等),需要先引入 polyfill 再使用 axios,例如使用es6-promise这类 polyfill 包。现代浏览器与 Node.js 均已原生支持 Promise,这一限制仅在面向很老的环境时需要考虑。

小结

  • 每个 axios 请求返回标准 ES6 Promise,.then()收到AxiosResponse.catch()收到携带code/response/config/request字段的AxiosError
  • resolve/reject 由 settle.js 依据validateStatus判定,默认200 <= status < 300才成功,4xx/5xx 分别以ERR_BAD_REQUEST/ERR_BAD_RESPONSEreject;
  • TypeScript 下使用AxiosPromise<T, D, P>可获得响应数据、请求体与 query 参数的完整类型,且请求参数可通过response.config回读;
  • async/await是推荐写法;并发场景按“全成功/容忍部分失败”选择Promise.allPromise.allSettled
  • 请求间的顺序依赖用.then()链即可,链上任意一环返回的 thenable 都会被自动等待,与 axios 内部拦截器管道共享同一套 Promise 组合规则。

【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询