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

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

2026-09-04 23:42:54作者:仰钰奇

axios 是一个基于 Promise 的 HTTP 客户端,它构建在 ES6 原生 Promise API 之上:每一次请求都会返回一个 Promise,它要么以响应对象(response object)结算(resolve),要么以错误拒绝(reject)。对于不支持原生 Promise 的环境,需要自行 polyfill(例如 es6-promise)。本文基于仓库中的 Promises 官方文档,结合 Axios.jssettle.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.jsadapter(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

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384