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 API 之上,每一次请求都返回一个标准的 Promise——成功时 resolve 为AxiosResponse响应对象,失败时 reject 为AxiosError。本文以官方文档中的 Promise 指南为主体,系统讲解then/catch/finally、async/await、Promise.all并行请求与请求链的完整用法,并结合lib/core源码揭示"一个请求究竟在什么时机被 resolve、在什么条件下被 reject"的底层机制,帮助读者从调用层面深入到实现层面真正掌握 Axios 的异步编程模型。
Axios 基于原生 Promise API 构建
Axios 的每个请求方法(axios.get、axios.post、axios.request等)返回的都是 JavaScript 标准的 Promise 对象,其语义是:resolve 为一个响应对象,或 reject 为一个错误。这意味着你可以直接把它交给任何遵循 Promise/A+ 规范的库和语言特性来处理,无需 Axios 特有的异步 API。
适用前提:如果你的运行环境不支持 ES6 原生 Promise(例如某些旧版浏览器),需要先引入一个 polyfill,例如es6-promise,再使用 Axios。对于现代 Node.js 与浏览器环境,Promise 是内置能力,无需额外处理。
从源码结构看,这一"标准 Promise"承诺贯穿整条请求链路:
- lib/core/Axios.js 中
Axios.prototype.request是一个async方法(见 request 方法),其内部_request通过Promise.resolve(config)起步,再逐个.then()串联请求拦截器、dispatchRequest和响应拦截器(见 Promise 链构建),最终返回的正是这条链末端的 Promise; - 各 HTTP 方法别名(
get/post/put/patch等)都只是对this.request(...)的包装(见 方法别名定义),因此所有入口返回的 Promise 行为完全一致。
TypeScript 中的AxiosPromise<T, D, P>
对于 TypeScript 集成,类型定义中的AxiosPromise<T, D, P>即Promise<AxiosResponse<T, D, {}, P>>,定义在 index.d.ts。三个泛型分别对应响应数据类型、请求体类型和查询参数类型,请求侧数据会保留在response.config中:
declare const search: AxiosPromise<SearchResponse, RequestBody, SearchParams>; search.then((response) => { response.data; // SearchResponse response.config.data; // RequestBody | undefined response.config.params; // SearchParams | undefined });这个类型设计的好处是:在then回调里不仅能拿到强类型的data,还能回溯当次请求实际发出的 body 和 query 参数(例如用于日志、重试或埋点),而不必在闭包里额外保存一份副本。
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与catch各自捕获的是什么:
then回调收到的response是完整的AxiosResponse(包含data、status、headers、config、request),而不只是data。浏览器端测试 promise.browser.test.js 中的 "should provide succinct object to then" 用例就验证了这一点:await axios('/foo')之后,response.data.hello、response.status、response.headers['content-type']与response.config.url均可访问。catch回调收到的错误由settle统一产生。lib/core/settle.js 根据config.validateStatus判定结果:状态码通过校验(默认 2xx)则调用resolve(response);否则调用reject(new AxiosError(...)),且当状态码落在 400–499 区间时,错误码被标记为ERR_BAD_REQUEST,否则为ERR_BAD_RESPONSE,错误信息为'Request failed with status code ' + status。这也解释了为什么在catch中可以通过error.response拿到服务端响应体(settle将config、request、response一并传入了AxiosError构造器)。- 此外,请求被取消(
AbortController或CancelToken)时,被拒绝的不是普通的 HTTP 状态错误,而是CanceledError——dispatchRequest在执行前后都会检查取消状态(见 throwIfCancellationRequested),可用axios.isCancel(error)区分。
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; } }几个与源码对应的实践要点:
await axios.get(...)解出的对象即AxiosResponse,业务层通常需要的是response.data,注意不要遗漏这一层;- 上例中
catch后重新throw error是常见模式:在统一记录日志/上报的同时,让调用方仍能感知失败,避免静默吞错; - 由于
Axios.request本身是 async 函数(lib/core/Axios.js),它还会在捕获到错误后尝试为错误对象补全/合并stack,因此在async/await场景下拿到的错误堆栈信息更利于排查问题。
并行请求: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); } });两种策略的取舍可以概括为:Promise.all适合"数据有强依赖、缺一即失败"的场景(如订单详情必须包含商品与库存);Promise.allSettled适合"各数据块相互独立、可降级展示"的场景。
除了直接使用原生 API,Axios 还在入口层暴露了等价便捷方法:lib/axios.js 中定义了axios.all = (promises) => Promise.all(promises)和axios.spread = spread(底层实现在 lib/helpers/spread.js)。promise.browser.test.js 中的两个用例分别验证了axios.all([true, 123])的结果聚合,以及axios.all([...]).then(axios.spread((a, b) => ...))将数组参数"摊开"传给回调的能力——后者在"两个请求的返回需要作为两个独立参数使用"时比手动解构更简洁。
链式请求:用 then 串联依赖型调用
当第二个请求依赖第一个请求的结果时,可以用.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回调中返回一个新的 Promise(即新的 Axios 请求),原链条会自动等待它完成后再把结果传给下一个.then——这是 Promise 链"透明展平"的机制,无需嵌套回调;- 解构
{ data: user }直接取响应体,保持链条简洁; - 链末的单个
.catch(console.error)可以捕获整条链上任意一环的失败:第一段请求失败、第二段请求失败、甚至.then回调内抛出的同步异常,都会汇聚到该catch。
对照 dispatchRequest 的内部实现可以确认,链中的每个请求节点都是"完整的一次请求":适配器返回的 Promise 在成功分支中执行transformResponse(默认包含 JSON 解析)并把headers规范化为AxiosHeaders实例;在失败分支中,若错误带有response,也会对其data执行同样的转换后再Promise.reject(reason)(见 onAdapterRejection)。也就是说,链式请求中每一环拿到的response.data都已经是解析后的 JSON,且catch分支中的error.response.data(如存在)同样是被转换过的数据。
小结
- 统一语义:所有请求入口都返回标准 Promise,resolve 为
AxiosResponse、reject 为AxiosError(HTTP 状态错误或CanceledError),可直接与then/catch/finally、async/await及一切 Promise 组合原语配合使用; - 失败判定点唯一且可定制:是否 reject 由
validateStatus决定,实现集中在 settle.js,4xx/5xx 分别对应ERR_BAD_REQUEST/ERR_BAD_RESPONSE; - 组合能力完整:
Promise.all(快速失败)与Promise.allSettled(容忍部分失败)覆盖并行场景,.then返回请求即可实现依赖型链式调用,axios.all/axios.spread提供等价便捷方法; - 类型友好:
AxiosPromise<T, D, P>将请求体与查询参数保留在response.config中,便于在强类型环境中做日志与重试。
【免费下载链接】axiosPromise based HTTP client for the browser and node.js项目地址: https://gitcode.com/GitHub_Trending/ax/axios
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考