news 2026/9/5 16:09:35

Axios 的 Promise 编程模型:从 then/catch/finally 到 async/await 的完整实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Axios 的 Promise 编程模型:从 then/catch/finally 到 async/await 的完整实践指南

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.getaxios.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 )); } }

这里揭示了两个实践要点:

  1. HTTP 4xx/5xx 不会自动 resolve:只有当validateStatus(response.status)为真时(默认规则在 lib/defaults/index.js 中定义)才 resolve;否则 reject 一个AxiosError,并根据状态码区分ERR_BAD_REQUEST(4xx)与ERR_BAD_RESPONSE(5xx)。
  2. 可以在.catch()中拿到完整上下文:被 reject 的AxiosError携带configrequestresponse属性,因此文档中.catch((error) => { console.error("Request failed:", error.message); })这类写法可以进一步访问error.response.statuserror.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的类型正是第一个泛型参数Tresponse.configInternalAxiosRequestConfig<D, P>,因此config.data(请求体)与config.params(查询参数)分别携带DP类型。这对"根据本次请求实际发出去什么来推断响应类型"的场景非常有用——同一个接口,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()中处理成功分支,参数是完整的AxiosResponsedatastatusstatusTextheadersconfig);
  • .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; } }

这种写法有两个工程价值:

  1. 控制流更直观try/catch覆盖了同步代码和异步错误,避免回调嵌套;
  2. 错误可以精确传播:示例中catch里先记录日志再throw error,保持了"记录但不吞掉"的错误处理习惯。由于dispatchRequestsettle中 reject 的都是结构化的AxiosError,上游catch可以进一步通过error.isAxiosErrorerror.codeerror.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 }结构:成功项的valueAxiosResponse(取.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/responseAxiosError(或取消时的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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 16:07:37

STM32F4硬件时间戳实现IEEE 1588 PTP主时钟

简介&#xff1a;本资源是面向嵌入式开发工程师与工业通信系统设计者的STM32 F4系列PTP&#xff08;IEEE 1588精密时间协议&#xff09;完整实现方案&#xff0c;解决高精度网络时钟同步在自动化、电力监控、音视频传输等场景下的落地难题。压缩包含515个文件&#xff0c;以151…

作者头像 李华
网站建设 2026/9/5 16:07:20

Skyvern实战笔记:3步在本地跑通AI浏览器自动化

Skyvern实战笔记&#xff1a;3步在本地跑通AI浏览器自动化 【免费下载链接】skyvern Automate browser based workflows with AI 项目地址: https://gitcode.com/GitHub_Trending/sk/skyvern 财务同事需要从十几个供应商后台逐个导出上月的发票 PDF&#xff0c;而每个后…

作者头像 李华
网站建设 2026/9/5 16:07:11

RAG-Anything自定义模态处理器:20行代码跑通

RAG-Anything自定义模态处理器&#xff1a;20行代码跑通 【免费下载链接】RAG-Anything "RAG-Anything: All-in-One RAG Framework" 项目地址: https://gitcode.com/GitHub_Trending/ra/RAG-Anything 解析出的文档里混着一种内置链路没覆盖的内容类型——音频…

作者头像 李华
网站建设 2026/9/5 16:06:58

智能体自行获取GPU算力风险与分层护栏设计实践

Aravind Srinivas 附议 Ilya Sutskever&#xff1a;智能体可能自行获取 GPU 算力&#xff0c;需设护栏最近技术圈里有一条讨论热度很高&#xff1a;AI 时代的两位关键人物 —— OpenAI 联合创始人 Ilya Sutskever 与 Perplexity CEO Aravind Srinivas —— 先后表达了对“智能体…

作者头像 李华
网站建设 2026/9/5 16:03:27

用智能体团队开发Grok Bot:多智能体协作实战指南

在开始接触“用智能体团队开发智能体”这个题目时&#xff0c;很多人会下意识地以为它只是“多开几个 AI 对话框&#xff0c;让它们互相聊聊天”。但如果你真正上手搭建过一套 Agent 团队&#xff0c;就会发现事情远没有这么简单。这里的核心不是“有多少个 AI 在干活”&#x…

作者头像 李华
网站建设 2026/9/5 15:58:44

spotDL 五步教程:把 Spotify 歌单免费下载到本地的完整指南

spotDL 五步教程&#xff1a;把 Spotify 歌单免费下载到本地的完整指南 【免费下载链接】spotify-downloader Download your Spotify playlists and songs along with album art and metadata (from YouTube if a match is found). 项目地址: https://gitcode.com/GitHub_Tre…

作者头像 李华