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 是一个基于 Promise 的 HTTP 客户端,它构建在 ES6 原生 Promise API 之上:每一次请求都会返回一个 Promise,它要么以响应对象(response object)结算(resolve),要么以错误拒绝(reject)。对于不支持原生 Promise 的环境,需要自行 polyfill(例如 es6-promise)。本文基于仓库中的 Promises 官方文档,结合 Axios.js、settle.js 等源码,系统讲解 axios 的 Promise 编程模型——包括.then()/.catch()/.finally()链式处理、async/await推荐写法、Promise.all并行请求、Promise.allSettled容错处理、请求链式编排,以及AxiosPromise泛型在 TypeScript 中的类型保真机制,并深入源码说明请求 Promise 是如何被 resolve 或 reject 的。
请求的返回类型:一次请求就是一个 Promise
axios 的每个请求方法(axios.get、axios.post等)本质上都是对Axios.prototype.request的包装,而request本身是一个async方法,因此调用返回的就是一枚标准的 ES6 Promise:
axios.get("/api/users"); // Promise<AxiosResponse>从 lib/core/Axios.js 的源码结构看,各 HTTP 方法最终都汇聚到this.request(...):
// lib/core/Axios.js(节选) async request(configOrUrl, config) { try { return await this._request(configOrUrl, config); } catch (err) { // ... 补充/合并错误堆栈后重新 throw throw err; } }request内部的 catch 分支还做了一件对排错很有用的事:当捕获到Error实例且其stack缺失或被截断时,会利用Error.captureStackTrace生成当前调用栈并合并进err.stack。这意味着.catch((error) => ...)中拿到的错误对象通常带有更完整的堆栈信息,便于定位抛出点。
而真正决定"这条 Promise 链如何流动"的是_request中的拦截器链编排(lib/core/Axios.js 第 192-209 行附近):
let promise; let i = 0; let len; 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; }也就是说:当存在异步请求拦截器时,axios 从Promise.resolve(config)出发,依次.then(拦截器 fulfilled, 拦截器 rejected),中间插入dispatchRequest,最后挂上响应拦截器——整条链上任何一个环节 reject,最终都会传播到你await后的 catch 或.catch()。这正是"axios 返回标准 Promise"这一承诺在源码层面的实现方式。
Promise 的 resolve / reject 由谁决定
请求发出后,Promise 的最终结算发生在适配器层。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 )); } }这里揭示了两个实践要点:
- HTTP 4xx/5xx 不会自动 resolve:只有当
validateStatus(response.status)为真时(默认规则在 lib/defaults/index.js 中定义)才 resolve;否则 reject 一个AxiosError,并根据状态码区分ERR_BAD_REQUEST(4xx)与ERR_BAD_RESPONSE(5xx)。 - 可以在
.catch()中拿到完整上下文:被 reject 的AxiosError携带config、request、response属性,因此文档中.catch((error) => { console.error("Request failed:", error.message); })这类写法可以进一步访问error.response.status、error.config等。
此外,lib/core/dispatchRequest.js 中adapter(config).then(onAdapterResolution, onAdapterRejection)还保证了:即使适配器 reject(网络错误、取消等),只要错误对象上挂了response,也会对其执行transformResponse数据转换,然后Promise.reject(reason)传播下去——所以"失败响应体也能反序列化"这一行为是有源码背书的。
TypeScript 集成:AxiosPromise 的类型保真
对于 TypeScript 项目,index.d.ts 第 580 行定义了核心类型:
export type AxiosPromise<T = any, D = any, P = any> = Promise<AxiosResponse<T, D, {}, P>>;AxiosPromise<T, D, P>就是AxiosResponse<T, D, {}, P>的 Promise,其中三个泛型参数分别对应:响应数据类型 T、请求体类型 D、查询参数类型 P。它的关键价值在于:请求的数据和参数会保留在response.config上,使类型系统能够追踪一次请求的"完整往返"。
AxiosResponse接口的定义(index.d.ts 第 515-522 行):
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; }文档给出的示例展示了三个泛型参数如何在实际业务类型中落地:
declare const search: AxiosPromise<SearchResponse, RequestBody, SearchParams>; search.then((response) => { response.data; // SearchResponse response.config.data; // RequestBody | undefined response.config.params; // SearchParams | undefined });从上面的接口定义可以验证:response.data的类型正是第一个泛型参数T;response.config是InternalAxiosRequestConfig<D, P>,因此config.data(请求体)与config.params(查询参数)分别携带D与P类型。这对"根据本次请求实际发出去什么来推断响应类型"的场景非常有用——同一个接口,config里保留了本次调用的真实载荷类型。
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(data、status、statusText、headers、config);.catch()中处理所有失败分支——包括网络错误、超时、取消(CanceledError)、以及validateStatus判定失败的 4xx/5xx 响应;.finally()适合放置与成败无关的收尾逻辑,如关闭 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; } }这种写法有两个工程价值:
- 控制流更直观:
try/catch覆盖了同步代码和异步错误,避免回调嵌套; - 错误可以精确传播:示例中
catch里先记录日志再throw error,保持了"记录但不吞掉"的错误处理习惯。由于dispatchRequest与settle中 reject 的都是结构化的AxiosError,上游catch可以进一步通过error.isAxiosError、error.code、error.response做精细分支。
并行请求: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。如果需要处理部分失败(部分接口 5xx 不应导致整页崩溃),应改用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", value/reason }结构:成功项的value是AxiosResponse(取.data),失败项的reason是 reject 出的错误对象(通常是AxiosError,取.message或其他属性)。两种方式的取舍可以概括为:
| 方法 | 失败行为 | 适用场景 |
|---|---|---|
Promise.all | 任一失败即整体 reject | 多个请求是"全有或全无"的强依赖组合 |
Promise.allSettled | 等待全部结束,逐项报告结果 | 聚合多个独立数据源,允许局部降级 |
请求链式编排:串联依赖请求
你可以链式调用.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 请求(而不是先赋值再 resolve),Promise 链会自动等待该新请求完成后才进入下一个.then()。链上任意一环 reject(包括第二步请求失败),都会跳过中间的成功回调直接落到链尾的.catch()。对应的async/await等价写法是:
try { const { data: user } = await axios.get("/api/user/1"); const { data: posts } = await axios.get(`/api/posts?userId=${user.id}`); console.log("Posts for user:", posts); } catch (error) { console.error(error); }两种写法在 Promise 语义上完全等价;选择哪一种是团队风格问题,但错误处理路径(catch 的位置、是否 rethrow)应当保持一致。
小结
- axios 的每次请求都是标准 ES6 Promise:成功 resolve 完整的
AxiosResponse,失败 reject 携带config/response的AxiosError(或取消时的CanceledError); - 4xx/5xx 是否算"失败"由
validateStatus决定,结算逻辑在 lib/core/settle.js 中; - 推荐用
async/await + try/catch组织控制流,用.finally()收尾; - 强依赖的并行请求用
Promise.all,允许局部失败的聚合用Promise.allSettled; - 依赖型顺序请求用
.then()链式编排或等价的await序列; - TypeScript 中用
AxiosPromise<T, D, P>保留响应类型、请求体与查询参数的完整类型链路。
想进一步理解拦截器如何嵌入这条 Promise 链,可以继续查看 interceptors.md 文档与 lib/core/Axios.js 中_request的链式编排实现;错误处理细节可参考 error-handling.md。
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考