主题
创建 axios 实例与拦截器(Day19 2/3)
本页对应 Day19-Fetch&Axios,共 6 段视频,约 1 小时 32 分。
学习目标
- 能够配置 VSCode 用户代码片段,为 HTML/JS 设置自定义前缀并生成包含转义双引号的代码模板,并通过快捷键验证插入结果。
- 能够使用 Fetch 发送 GET、POST、PUT、DELETE 请求,正确设置
method、headers、body,并使用response.json()、async/await处理响应及超时、404 等异常情况。 - 能够说明 axios 的特点与引入方式(CDN、script 标签、npm 导入),验证全局
axios为函数并完成基本请求发送。 - 能够使用 axios 的多种调用方式(如
axios(config)、axios(url, config)、axios.get、axios.request)发送请求,并正确读取response.data、status、headers、config、request。 - 能够使用
axios.create创建实例并配置baseURL、timeout等请求配置项,使用拦截器处理请求/响应,使用取消请求和批量发送请求并处理响应。 - 能够根据 axios 方法总结和请求配置项,为给定请求需求选择合适的方法与配置,并解释
baseURL、headers、data、timeout等关键配置的作用。
知识点
为什么需要自定义 axios 实例
为什么需要: 一个项目里,不同接口的请求配置往往不一样。按本节笔记的说法,项目中有部分接口需要的配置与另一部分接口需要的配置不太一样。如果每次请求都临时写一遍配置,既重复又容易漏。
怎么想: 把“公共配置”提前固定下来,让某一类接口共用一套配置。笔记给出的解决方式是:创建 2 个自定义 axios 新实例,每个都有自己特有的配置,分别应用到不同要求的接口请求中。
怎么做: 用 axios.create(config) 根据指定配置创建一个自定义 axios 实例。每个自定义实例都有自己的配置。
易错点: axios.create 的作用是“创建实例”,不是“发送请求”。创建完还要调用实例上的 get、post 等方法。
实例与默认 axios 的关系
为什么需要: 初学时容易把“默认 axios”和“axios 实例”混为一谈,导致不知道什么时候该用哪个。
怎么想: 按本节笔记的表述:axios 实例只是没有取消请求和批量发请求的方法,其它所有方法与默认 axios 本身是一样的。也就是说,日常的 get、post 等用法可以迁移到实例上。
怎么做: 先把实例理解成“带了一套固定配置的 axios”。默认 axios 能发的普通请求,实例也能按同样思路发;只是本节笔记特别说明它少了取消请求和批量发请求的方法。
易错点: 不要因为“实例”两个字就以为 API 完全变了。笔记强调的是“配置独立”和“少两个方法”,不是所有方法都不同。
创建实例并发送请求
为什么需要: 笔记给出的代码展示了最小可用写法:先创建实例,再用实例发请求。
怎么想: create 的参数是一个配置对象,示例里放了 baseURL 和 timeout。创建出来的 instance 就带着这两个配置。之后调用 instance.get(url),返回 Promise,用 then 处理响应。
怎么做: 保留本节代码:
js
const instance = axios.create({
baseURL: 'https://example.com/api/',
timeout: 1000,
});
instance.get(url).then(res => {
console.log(res);
});这段代码的含义是:创建实例时指定基础地址和超时时间;请求时只传 url;响应回来后打印 res。
易错点: timeout: 1000 的单位是毫秒,表示超过约 1 秒未完成会失败。转写稿里演示超时时,把服务端延迟设为 3000、客户端超时设为 2000,结果进入失败回调,错误对象的 message 提示超时,config 里还能看到请求配置。由此可推导:实例上的 timeout 不是摆设,调试时应查看错误对象里的 message 和 config,确认配置是否生效。另外,then 里的 res 是响应对象;转写稿也说明 data 才是响应体,若只要数据应看 res.data。
项目中两个实例如何分工
为什么需要: 笔记的需求是“部分接口配置与另一部分接口配置不太一样”。如果只用一个默认 axios,每处都要重复写不同配置,维护成本高。
怎么想: 按笔记的解决思路:创建 2 个自定义 axios 新实例,每个都有自己特有的配置。比如一个实例负责一类接口,另一个实例负责另一类接口;调用时各用各的实例。
怎么做: 可以按同样的 axios.create({...}) 方式创建两次,分别得到两个实例,再把它们应用到不同要求的接口请求中。本节笔记只给了一个实例代码,所以先掌握“一个实例怎么建、怎么发请求”,再按同一模式扩展为两个。
易错点: 不要把两个实例的配置混用;也不要在每次请求里重复写实例已经固定好的配置。转写稿在讲基本使用时反复演示 baseURL、timeout 等配置项,并说明未设置的配置会有默认值。放到实例里理解就是:实例配置应集中、清晰,避免“这个请求到底用了哪套配置”说不清。
调试与常见坑
为什么需要: 创建实例后请求失败,常见原因不是实例本身,而是配置、地址或超时。
怎么想: 先确认请求有没有发出去,再看响应或错误对象。转写稿的调试过程是先打开浏览器网络面板观察请求,再在 then/catch 中打印结果;超时演示则通过错误对象定位问题。
怎么做: 检查 baseURL 是否写对、url 是否传对、timeout 是否过小;失败时打印错误对象,重点看 message 和 config。
易错点: 把 create 当成请求方法;忘记调用 instance.get;把 res 当成响应体;忽略 timeout 单位;忘记本节笔记的限定——实例没有取消请求和批量发请求的方法。按笔记范围使用,不要凭空扩展。
拦截器先解决什么问题
拦截器(不是“蓝截器”)是 axios 在请求或响应被 then 或 catch 处理前,留给我们的一道“关卡”。转写稿里把它类比成 Express 的中间件:请求要经过它,响应也要经过它,所以可以在这里做统一处理。为什么需要它?因为实际项目里很多逻辑是重复的,比如每个请求都想带同一份配置、每个响应都想先做一次检查、每个错误都想统一提示。如果每发一次请求都复制一遍,代码会散、会漏、也难维护。怎么想?把拦截器看成“请求发出前”和“响应进入业务回调前”的两个处理点。怎么做?请求拦截器拿到 config,处理完必须返回 config;响应拦截器拿到 response,处理完必须返回 response;错误分支则用 Promise.reject(error) 继续把错误往下传。易错点是:拦截器函数不是随便写个 console.log 就完事,忘记 return 会让后面的请求或响应拿不到数据。
添加请求拦截器和响应拦截器
笔记给出的基本写法如下:
js
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});为什么需要两个回调?因为 use 的成功回调和失败回调分别对应“正常走到这里”和“前面已经出错”的情况。怎么想?请求拦截器的成功回调参数是 config,它代表这次请求的配置;响应拦截器的成功回调参数是 response,它代表响应对象。怎么做?在成功回调里改完、看完,把原对象返回;在失败回调里不要吞掉错误,而要 return Promise.reject(error)。调试时可以分别在两个拦截器里打日志,再发一次请求,观察日志出现的先后。易错点:请求拦截器里不能返回 response,响应拦截器里也不要以为拿到的是响应体本身;按笔记的流程,请求拦截器传递的是 config,响应拦截器传递的是 response。
移除拦截器
如果某个拦截器只是临时启用,后面要撤掉,可以这样写:
js
// 定义拦截器的时候使用变量记录拦截器的唯一标识
const reqInterceptor = axios.interceptors.request.use(function () {/*...*/});
const resInterceptor = axios.interceptors.response.use(function () {/*...*/});
// 根据唯一标识移除拦截器
axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.response.eject(resInterceptor);为什么需要移除?因为拦截器一旦 use 就会挂在实例上,重复注册会叠加执行;在条件切换、页面卸载或测试场景里,需要把不用的拦截器撤掉。怎么想?use 的返回值就是拦截器的唯一标识,移除时要拿这个标识去 eject。怎么做?先用变量保存 use 的返回值,再调用对应 request.eject 或 response.eject。易错点:不保存返回值就没法精确移除;请求拦截器和响应拦截器要分别从 interceptors.request、interceptors.response 上移除,不能混着写。
自定义实例上的拦截器
拦截器也可以加在自定义 axios 实例上:
js
const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});为什么需要?因为不同模块可能需要不同的默认配置和不同的拦截逻辑,挂在自定义实例上可以隔离。怎么想?axios.create() 创建的是一个独立实例,拦截器应挂在 instance.interceptors 上,而不是总挂在全局 axios.interceptors 上。怎么做?先创建实例,再按请求/响应分别 use。易错点:如果你写的是 instance,却去 axios.interceptors 上移除,那移除的不是同一个东西;全局默认实例和自定义实例要分清。
执行流程与 Promise 串连
笔记特别强调:调用 axios() 并不是立即发送 ajax 请求,而是要经历一个较长的流程:
1. 流程: 请求拦截器2 -> 请求拦截器1 -> 发ajax请求 -> 响应拦截器1 -> 响应拦截器2 -> 请求的回调
2. 注意: 此流程是通过 Promise 串连起来的, 请求拦截器传递的是 config, 响应拦截器传递的是 response为什么顺序是这样?因为 axios 内部用 Promise 把拦截器串成链:请求拦截器后添加的先靠近请求发送端,所以先执行“2”再执行“1”;响应拦截器先添加的先靠近响应返回端,所以先执行“1”再执行“2”。怎么想?不要死记“谁先谁后”,而要看它处在链的哪一端。调试时可以在请求拦截器1、请求拦截器2、响应拦截器1、响应拦截器2里分别打日志,发一次请求,观察日志顺序是否与笔记流程一致。怎么做?写拦截器时始终保证返回值正确:请求链返回 config,响应链返回 response,错误链 reject。易错点:把顺序记反;在拦截器里忘记返回;把“请求拦截器”和“响应拦截器”的参数弄混;以为 axios() 一调用就立刻发请求,从而误判日志顺序。把这几条检查清楚,拦截器这一节就稳了。
本节目标:把“取消请求”放回真实开发里理解
这一节讲 axios 的取消请求。转写稿里反复提到一个底层事实:原生 XMLHttpRequest 有 abort 方法,可以把请求到一半的请求中断掉;而 fetch 在“请求中断”这块相对薄弱。axios 对原生 XHR 能力做了封装,所以我们可以用更统一的方式取消请求。理解这一点后,再看笔记里的 cancelToken 流程,就不会觉得它是一串孤立步骤。
为什么需要:请求发出去后,业务时机可能已经变了,旧请求再回来也没有意义。怎么想:取消不是“删除响应”,而是主动告诉请求层:这个请求不要了。怎么做:本节按笔记使用 cancelToken。易错点:不要把所有请求都加取消逻辑,只有确实存在“后面可能不要”的请求才需要。
基本流程:四步要对应起来看
笔记给出的基本流程是:
1. 请求配置项中配置 cancelToken 对象
2. 用变量保存用于取消请求的 cancel 函数
3. 在后面特定时机调用 cancel 函数实现取消请求
4. 在错误回调中判断如果 error 是 cancel, 做相应处理为什么需要:这四步分别解决“声明可取消”“拿到取消能力”“触发取消”“识别取消结果”。怎么想:cancelToken 不是立刻取消,而是先在请求配置里登记一个“取消令牌”;真正的取消动作要等外部调用 cancel。怎么做:先配置,再保存,再调用,最后在 catch 里判断。易错点:只配置 cancelToken 不保存 cancel,后面就没有取消入口;保存了却忘记在 catch 里判断,取消会被当成普通失败。
配置 cancelToken,并用变量保存 cancel 函数
为什么需要:取消动作通常发生在请求发出之后,所以必须把 cancel 函数保存到外部作用域,后面才能调用。怎么想:new axios.CancelToken(cb => { cancel = cb; }) 的本质是“把取消函数交出来”,我们用一个变量接住它。怎么做:保留笔记代码:
js
// 定义变量用于保存 cancel 函数
let cancel = null;
axios({
method: "GET",
url:'api.emaple.com',
// 配置取消请求的选项
cancelToken: new axios.CancelToken(cb => {
cancel = cb;
})
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
console.log(axios.isCancel(err));// 判断错误信息是否由取消请求触发的
});
// 如果需要取消,只需调用 cancel 函数
cancel();易错点:cancel 要先声明;cancelToken 拼写不要错;回调里是 cancel = cb,不是立刻执行 cb()。另外原笔记 URL 写作 api.emaple.com,这里按原样保留,实际项目要写正确地址。
在特定时机调用 cancel
为什么需要:取消请求的关键不是“能取消”,而是“什么时候取消”。怎么想:cancel 是一个函数,调用它就等于发出取消信号。怎么做:在业务时机成熟时执行 cancel();如果前面可能没配置成功,实际代码里可先判断 if (cancel) cancel()。易错点:不要在 cancelToken 配置回调里直接调用,否则请求可能刚发出就被取消;也不要重复取消后还期待 then 继续执行。
在错误回调中识别取消:isCancel
为什么需要:取消请求会让 Promise 进入失败分支,如果不区分,就会把“用户主动取消”误报成“请求失败”。怎么想:catch 里的 err 可能是网络错误,也可能是取消错误;axios.isCancel(err) 就是用来判断后者。怎么做:按笔记在 catch 中打印 err 和 axios.isCancel(err)。调试时可以观察到:不调用 cancel() 时走 then;调用 cancel() 后走 catch,并且 isCancel 为 true。易错点:isCancel 是 axios 提供的方法,不要自己猜错误字符串;取消分支里不要再弹“请求失败”的强提示。
调试顺序与常见坑
建议按这个顺序验证:先发请求,不调用 cancel,确认 then 能拿到响应;再在请求发出后调用 cancel(),观察是否进入 catch;最后在 catch 里用 axios.isCancel(err) 区分取消和真实错误。为什么需要:这样能确认每一步职责,而不是把四步混在一起。怎么想:先验证“正常请求”,再验证“取消请求”,最后验证“错误识别”。怎么做:保留上面的完整代码,分两次运行。易错点:把取消错误当成网络错误;忘记保存 cancel;在 then 里继续处理已经取消的响应;混用原生 abort 和 axios 的 cancelToken。转写稿提到 XHR 的 abort 可以中断请求,但本节用的是 axios 封装后的取消方案,二者不要混写。
小结:取消请求的检查清单
看到“取消请求”,先问:这个请求后面真的可能不要吗?再确认:cancelToken 配了吗?cancel 存了吗?取消时机对吗?catch 里判断 isCancel 了吗?把这四点串起来,笔记里的代码就不是死步骤,而是一条完整的控制链:请求发出 → 保存取消入口 → 业务时机触发 → 失败回调识别取消。
为什么需要批量发送请求
为什么需要: 当页面要同时拿多条互不依赖的数据时,如果写成一个请求成功后再发下一个,就会把本可并行的等待串起来,总耗时变长。批量发送的思路是先把多个请求都发出去,再统一等待结果。
怎么想: axios.get() 返回的是 Promise。转写稿里也强调过,axios 返回 Promise 对象,所以可以用 then/catch 处理。既然如此,就可以把多个 Promise 聚合成一个 Promise,等它们全部完成后再统一处理响应。
怎么做: 本节笔记的核心代码如下:
js
const r1 = axios.get("http://127.0.0.1:3000/computers/1");
const r2 = axios.get("http://127.0.0.1:3000/computers/2");
const r3 = axios.get("http://127.0.0.1:3000/computers/3");
axios
.all([r1,r2,r3])
.then(axios.spread((s1,s2,s3) => {
console.log(s1,s2,s3);
}))
.catch(err => {
console.log(err);
})易错点: 不要写成“第一个请求 then 里再发第二个请求”的串行结构;批量请求是并发发出、统一等待,不是把请求按顺序排队。
先把每个请求变成 Promise
为什么需要: axios.all 聚合的不是普通数据,而是多个请求返回的 Promise。所以要先理解 r1、r2、r3 是什么。
怎么想: axios.get(...) 一调用,请求就已经发出,同时返回一个 Promise。r1 不是响应数据,而是“未来会拿到响应的凭据”。
怎么做: 前三行分别发出三个 GET 请求,并把返回的 Promise 存到 r1、r2、r3。这里请求地址分别是 /computers/1、/computers/2、/computers/3,对应 json-server 中的三条数据。
易错点: 不要在 r1 上直接取 data;也不要把地址写错或让 json-server 没启动。转写稿里反复提到跨域判断要看协议、域名、端口,如果页面和接口不同源,要先确认后端是否允许跨域。
用 axios.all 聚合等待
为什么需要: 三个请求发出后,需要等三个都完成再统一处理。如果只写一个 then,只能等一个请求。
怎么想: axios.all([r1,r2,r3]) 接收 Promise 数组,返回一个总 Promise;总 Promise 完成后,结果仍按数组顺序给出。也就是说,结果顺序对应 r1、r2、r3,不是按谁先完成排序。
怎么做: 用 axios.all([r1,r2,r3]).then(...) 等待全部完成。只要有一个请求失败,整体就会进入 catch,所以笔记最后写了 .catch(err => { console.log(err); })。
易错点: 不要漏掉 catch;不要以为 then 里拿到的是三个响应对象本身,实际拿到的是一个结果数组,需要下一步拆开。
用 axios.spread 拆开响应
为什么需要: then 收到的是结果数组,直接按下标取不够直观。axios.spread 可以把数组结果展开成多个参数。
怎么想: axios.spread((s1,s2,s3) => {...}) 中,s1 对应 r1,s2 对应 r2,s3 对应 r3。这里的 s1、s2、s3 是 axios 的响应对象,不是最终数据本身。
怎么做: 在 spread 回调里打印三个响应对象。若要取响应体,应使用 s1.data、s2.data、s3.data。转写稿中讲 axios 基本使用时也提到,响应对象里 data 才是响应体,其他还包括状态码、响应头、配置等信息。
易错点: spread 的参数个数要和请求个数对应;不要把 s1 直接当成数据;如果只关心数据,打印 s1.data 更清楚。
调试与验证
为什么需要: 批量请求涉及多个地址和多个 Promise,出错时要能快速定位是服务没启动、地址错、跨域,还是某个请求失败。
怎么想: 先看 Network 是否发出三个请求,再看 Console 是否打印三个响应对象;如果直接进 catch,就看错误对象里的信息和请求地址。
怎么做: 启动 json-server 后运行页面,打开开发者工具。正常情况下应能看到 /computers/1、/computers/2、/computers/3 三个请求,控制台输出三个响应对象。若出现跨域报错,先检查页面与接口的协议、域名、端口是否一致,并确认后端跨域配置。
易错点: 批量请求不是“事务”,一个失败会导致整体走 catch;如果希望单个失败不影响其他结果,需要另外设计处理方式。初学阶段先把本节代码跑通,理解“先发请求 → all 聚合 → spread 拆结果 → catch 兜底”的流程即可。
先看全貌:axios 方法清单怎么记

这张图对应本节附录的核心:axios 不只是一个“发请求的函数”,它本身还是一个带有很多方法和配置项的对象。转写稿里反复出现的“excels、access、axros”都是口误,正确写法是 axios;同理,“permiss”是 Promise,“接信格式”是 JSON 格式。先把清单分成四类,后面就不容易乱:通用调用与 request、快捷方法、默认配置/拦截器/实例、取消与并发。
为什么需要:真实项目里请求不止一种,配置也不止 URL 一个。怎么想:把 config 当成核心,其他方法都是它的语法糖或工程化补充。怎么做:axios(config) 是最本质的任意请求方式;axios(url[, config]) 是把 URL 独立出来;axios.request(config) 与 axios(config) 等价。转写稿演示过:
js
axios.get('/users/2', {
baseURL: 'http://localhost:8088',
timeout: 2000
}).then(res => {
console.log(res.data)
}).catch(err => {
console.log('请求失败', err)
})易错点:全局变量是小写 axios,写成大写拿不到;URL 不完整会直接报跨域或请求错误;axios(url, config) 不要和 axios.get(url, config) 混为一谈。
快捷方法:get/delete/post/put/patch 的参数差异
为什么需要:大多数请求就是 GET、POST、PUT、DELETE、PATCH,每次写 method 太啰嗦。怎么想:记住“有没有请求体”决定第二个参数是什么。怎么做:按笔记列出的签名使用——axios.get(url[, config])、axios.delete(url[, config]) 没有 data 参数;axios.post(url[, data, config])、axios.put(url[, data, config])、axios.patch(url[, data, config]) 的第二个参数是请求体数据,第三个才是配置项。转写稿里强调过:POST 要设置请求头,请求体格式要和后端约定一致,常见是 JSON。易错点:post(url, data, config) 不要把 config 写到第二个参数;笔记里 axios.patch 后面写作“发 put 请求”,按方法名应理解为 patch 请求,参数形式与 post/put 同类;delete 一般不需要请求体,别硬塞 data。
默认配置、拦截器、实例:工程化三件套
为什么需要:项目里 baseURL、超时、请求头往往要统一,不能每个请求都抄一遍。怎么想:默认配置管“所有请求的默认值”,拦截器管“请求前后统一处理”,实例管“不同模块用不同配置”。怎么做:axios.defaults.xxx 设置全局默认配置;axios.interceptors.request.use() 添加请求拦截器;axios.interceptors.response.use() 添加响应拦截器;axios.create([config]) 创建一个新的 axios 实例。转写稿用 baseURL 和 timeout 做过调试:服务端故意延迟 3000ms,客户端 timeout: 2000,结果进入失败回调,错误对象里有 message 和 config,能看出是哪个请求超时。易错点:全局默认配置会影响所有请求;实例配置和全局配置不要混着猜;拦截器是清单里的工程能力,笔记没有展开源码,学习时先记用途和签名,不要凭空补实现细节。
取消与并发:CancelToken/isCancel/all/spread
为什么需要:搜索框连续输入、页面切换时,旧请求返回可能覆盖新结果;多个接口又常常需要一起等。怎么想:取消请求要能创建“取消凭证”,还要能判断错误是不是取消造成的;并发要把多个 Promise 批量执行,再统一接收成功数据。怎么做:axios.CancelToken() 用于创建取消请求的 token 对象;axios.isCancel() 判断是否是一个取消请求的错误;axios.all(promises) 批量执行多个异步请求;axios.spread() 用来指定接收所有成功数据的回调函数的方法。易错点:取消错误不要当普通业务错误提示;all 接收的是 Promise 集合;spread 是配合批量结果使用的,不要和普通 then 混用。
调试过程:从请求发出去到拿到 res.data
转写稿的调试很值得复盘:一开始地址没写完整,浏览器报跨域错误,补全 URL 后请求才正常发出;用 Live Server 打开页面时,目录里文件变化会触发自动刷新,容易误以为请求“闪了一下”;设置 timeout 后,通过服务端延迟确认超时确实进入 catch。axios 返回 Promise,所以可以 .then,也可以 async/await:
js
async function getData() {
try {
const res = await axios.get('/users/2', {
baseURL: 'http://localhost:8088',
timeout: 2000
})
console.log(res.data)
} catch (err) {
console.log('请求失败', err)
}
}易错点:then 拿到的是响应对象,不是响应体;真正的数据在 res.data。响应对象里还有 status、statusText、headers、config、request 等信息。转写稿还提到 axios 会自动把 JSON 响应处理成对象,所以调试时先看 res.data,失败时看 err.message 和 err.config。
收束:怎么选
简单 GET 用 axios.get 最顺手;需要完整配置就用 axios(config) 或 axios.request(config);POST/PUT/PATCH 记住“data 在前、config 在后”;工程化统一用 defaults、拦截器和 create;取消和并发按 CancelToken/isCancel/all/spread 清单查。不要把方法表死记成步骤,抓住“config 是核心、快捷方法是语法糖、响应数据在 res.data”这三件事,附录就能真正用起来。
请求配置项是什么
为什么需要:axios 发请求时,URL、方法、请求头、请求体、超时等都要可配置;如果每处都手写 XHR,代码会重复且难维护。
怎么想:把一次请求看成一个“配置对象”,axios 将默认配置与调用时传入的配置合并,再交给适配器发送。转写稿中打印响应对象时能看到 config,里面既有我们传入的 url、method,也有大量默认值,这就是配置项在起作用。
怎么做:调用 axios(config),或 axios.get(url, config)、axios.request(config)。
易错点:不要以为没写的配置项不存在;它们通常有默认值。调试时看 response.config 或错误对象的 config,能确认最终生效值。
地址与方法:url、method、baseURL
为什么需要:同一项目常请求同一域名和端口,重复写完整 URL 很啰嗦。
怎么想:baseURL 抽出协议、域名、端口,url 只写路径;method 决定请求方式。转写稿中把 baseURL 设为服务地址,url 写 /2,就能请求 ID 为 2 的数据。
怎么做:baseURL: 'https://some-domain.com/api/',url: '/user',method: 'get'。
易错点:注意 baseURL 末尾斜杠与 url 开头斜杠的拼接;method 默认 get,但 DELETE、PUT、POST 要明确写。
数据转换与请求体:transformRequest、headers、data
为什么需要:后端通常只识别特定格式,例如 JSON;前端对象不能直接当请求体发。
怎么想:data 是请求体,headers 说明格式,transformRequest 可在发送前改数据。转写稿发 POST 时设置 Content-Type: application/json,并用 JSON.stringify 把对象转成 JSON 字符串。
怎么做:headers: {'X-Requested-With': 'XMLHttpRequest'};data: {firstName:'Fred'};需要时在 transformRequest 中返回字符串、ArrayBuffer 或 Stream。
易错点:transformRequest 只用于 PUT、POST、PATCH;data 也主要适用于这些方法。请求头与请求体格式必须匹配,否则后端可能识别不了。
参数与序列化:params、paramsSerializer
为什么需要:GET 请求常把查询参数拼到 URL 上,数组等复杂结构需要统一序列化规则。
怎么想:params 放查询参数对象,paramsSerializer 决定如何拼成字符串。
怎么做:params: {ID:12345};需要数组括号格式时用 Qs.stringify(params,{arrayFormat:'brackets'})。
易错点:params 不是请求体;请求体用 data。params 必须是普通对象或 URLSearchParams。
超时、凭证与认证:timeout、withCredentials、auth
为什么需要:请求不能无限等;跨域携带 Cookie 要显式允许;基础认证要带凭据。
怎么想:timeout 单位毫秒,超时后 Promise 进入失败;withCredentials 控制跨域凭证;auth 会生成 Authorization 头。转写稿把服务端延迟设为 3 秒、客户端超时设为 2 秒,结果进入 catch,错误信息提示 2000ms 超时,错误对象的 config 里还能看到本次配置。
怎么做:timeout:1000;withCredentials:false;auth:{username,password}。
易错点:timeout:0 表示不超时;auth 会覆写 headers 中自定义的 Authorization。
响应解析与校验:responseType、responseEncoding、validateStatus
为什么需要:服务器可能返回 JSON、文本、二进制;还要决定哪些状态码算成功。
怎么想:responseType 指定解析类型,validateStatus 返回 true 才 resolve。
怎么做:responseType:'json';validateStatus: status => status>=200 && status<300。
易错点:默认只把 2xx 视为成功;404 默认会 reject。转写稿中 fetch 对 404 仍可能 resolve,axios 的 validateStatus 逻辑不同,不能混用经验。responseEncoding 对 stream 或浏览器端请求通常忽略。
进度、长度与重定向:onUploadProgress、onDownloadProgress、maxContentLength、maxRedirects
为什么需要:上传下载要反馈进度;限制响应体大小;Node 端要控制重定向次数。
怎么想:进度回调接收原生 progressEvent;maxContentLength 限制响应内容;maxRedirects 只影响 Node。
怎么做:在配置中传回调函数;maxContentLength:2000;maxRedirects:5。
易错点:maxRedirects:0 表示不跟随重定向;浏览器端不要套用 Node 的重定向配置。
Node 专属与取消:socketPath、httpAgent、httpsAgent、proxy、cancelToken
为什么需要:Node 端可连 UNIX Socket、自定义 Agent、走代理;请求也可能需要取消。
怎么想:socketPath 与 proxy 二选一,二者都写时 socketPath 优先;cancelToken 用于取消。
怎么做:httpAgent: new http.Agent({keepAlive:true});proxy:{host,port,auth};cancelToken:new CancelToken(function(cancel){})。
易错点:这些多是 Node 专属;proxy.auth 会设置 Proxy-Authorization 并覆写同名头。
完整配置项代码
js
{
// `url` 是用于请求的服务器 URL
url: '/user',
// `method` 是创建请求时使用的方法
method: 'get', // default
// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return data;
}],
// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
// 对 data 进行任意转换处理
return data;
}],
// `headers` 是即将被发送的请求头
headers: {'X-Requested-With': 'XMLHttpRequest'},
// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object)或 URLSearchParams 对象
params: {
ID: 12345
},
// `paramsSerializer` 是一个负责 `params` 序列化的函数
// (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一:
// - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
// - 浏览器专属:FormData, File, Blob
// - Node 专属: Stream
data: {
firstName: 'Fred'
},
// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求话费了超过 `timeout` 的时间,请求将被中断
timeout: 1000,
// `withCredentials` 表示跨域请求时是否需要使用凭证
withCredentials: false, // default
// `adapter` 允许自定义处理请求,以使测试更轻松
// 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
adapter: function (config) {
/* ... */
},
// `auth` 表示应该使用 HTTP 基础验证,并提供凭据
// 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
// `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json', // default
// `responseEncoding` indicates encoding to use for decoding responses
// Note: Ignored for `responseType` of 'stream' or client-side requests
responseEncoding: 'utf8', // default
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
xsrfCookieName: 'XSRF-TOKEN', // default
// `xsrfHeaderName` is the name of the http header that carries the xsrf token value
xsrfHeaderName: 'X-XSRF-TOKEN', // default
// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
},
// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
// 对原生进度事件的处理
},
// `maxContentLength` 定义允许的响应内容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
// `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
// 如果设置为0,将不会 follow 任何重定向
maxRedirects: 5, // default
// `socketPath` defines a UNIX Socket to be used in node.js.
// e.g. '/var/run/docker.sock' to send requests to the docker daemon.
// Only either `socketPath` or `proxy` can be specified.
// If both are specified, `socketPath` is used.
socketPath: null, // default
// `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
// `keepAlive` 默认没有启用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
// 'proxy' 定义代理服务器的主机名称和端口
// `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
// 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
// `cancelToken` 指定用于取消请求的 cancel token
// (查看后面的 Cancellation 这节了解更多)
cancelToken: new CancelToken(function (cancel) {
})
}课堂画面






代码示例
示例 1:创建自定义 axios 实例 + 拦截器
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>axios 实例与拦截器</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="btn1">实例1 发请求</button>
<button id="btn2">实例2 发请求</button>
<button id="btnRemove">移除实例1的拦截器</button>
<script>
// 1. 根据指定配置创建两个自定义 axios 实例,每个实例都有自己的配置
const instance1 = axios.create({
baseURL: 'https://example.com/api/',
timeout: 1000
});
const instance2 = axios.create({
baseURL: 'https://example.com/api2/',
timeout: 5000
});
// 2. 为自定义 axios 实例添加拦截器,并用变量记录唯一标识
const reqInterceptor = instance1.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('请求拦截器:发送请求之前', config);
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
const resInterceptor = instance1.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('响应拦截器:拿到响应', response);
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
document.getElementById('btn1').onclick = () => {
instance1.get('users').then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
};
document.getElementById('btn2').onclick = () => {
instance2.get('users').then(res => {
console.log(res);
}).catch(err => {
console.log(err);
});
};
// 3. 根据唯一标识移除拦截器
document.getElementById('btnRemove').onclick = () => {
instance1.interceptors.request.eject(reqInterceptor);
instance1.interceptors.response.eject(resInterceptor);
console.log('实例1的拦截器已移除');
};
</script>
</body>
</html>关键行与易错点:
axios.create({...})创建的实例拥有自己独立的配置(如baseURL、timeout),不同实例互不影响,这正是“部分接口配置不一样”的解决方案。- 自定义实例只是没有取消请求和批量发请求的方法,其它方法与默认
axios本身一样。 - 请求拦截器必须
return config,响应拦截器必须return response;忘记return会导致后面的请求/回调拿不到配置或响应。 - 移除拦截器要先用变量保存
use()的返回值(唯一标识),再调用eject(id),直接调eject()传空是无效的。 - 多个拦截器的执行顺序是:请求拦截器2 → 请求拦截器1 → 发 ajax 请求 → 响应拦截器1 → 响应拦截器2 → 请求的回调;整条流程由 Promise 串连,请求拦截器传递的是
config,响应拦截器传递的是response。
示例 2:取消请求
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>axios 取消请求</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="btnSend">发送请求</button>
<button id="btnCancel">取消请求</button>
<script>
// 1. 定义变量用于保存 cancel 函数
let cancel = null;
document.getElementById('btnSend').onclick = () => {
axios({
method: 'GET',
url: 'https://example.com/api/users',
// 2. 配置取消请求的选项
cancelToken: new axios.CancelToken(cb => {
cancel = cb;
})
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
// 4. 判断错误信息是否由取消请求触发
console.log(axios.isCancel(err));
});
};
// 3. 在特定时机调用 cancel 函数实现取消请求
document.getElementById('btnCancel').onclick = () => {
if (cancel) {
cancel();
}
};
</script>
</body>
</html>关键行与易错点:
- 基本流程固定四步:配置
cancelToken→ 用变量保存cancel函数 → 特定时机调用cancel()→ 在错误回调中用axios.isCancel(err)判断是否为取消导致的错误。 cancel函数是在new axios.CancelToken(cb => { cancel = cb })的回调里拿到的,不执行这个构造就不会有cancel。- 取消请求后,请求的 Promise 走的是
catch,不是then,所以要取消逻辑必须写在catch里,并用axios.isCancel(err)区分“取消”和“真正的请求失败”。 - 每次发新请求都要重新创建
CancelToken(cancel会被重新赋值),否则调用的是上一次请求的取消函数。
示例 3:批量发送请求(批量处理响应)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>axios 批量请求</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<button id="btnAll">批量发送 3 个请求</button>
<script>
document.getElementById('btnAll').onclick = () => {
const r1 = axios.get('https://example.com/api/computers/1');
const r2 = axios.get('https://example.com/api/computers/2');
const r3 = axios.get('https://example.com/api/computers/3');
axios
.all([r1, r2, r3])
.then(axios.spread((s1, s2, s3) => {
console.log(s1, s2, s3);
}))
.catch(err => {
console.log(err);
});
};
</script>
</body>
</html>关键行与易错点:
axios.all([...])接收的是“请求 Promise 的数组”,不是 URL 数组;三个请求会同时发出,结果按传入顺序返回。axios.spread((s1, s2, s3) => {})的作用是把结果数组展开成多个参数,方便分别处理每个响应;不写spread时then拿到的是一个数组。- 批量请求是“全成功才算成功”,任意一个失败整体就进入
catch;axios.all本质是Promise.all的封装。 all和spread是axios本身的方法,自定义实例(axios.create()的返回值)没有这两个方法。
常见错误
1. 建了实例却仍在用 axios.get
- 现象:
instance上配的baseURL、timeout完全不生效,请求打到错误地址或没有超时限制。 - 原因:
axios与axios.create()返回的实例是两个独立对象,配置互不共享。 - 修法:实例化后统一从该实例调用方法;把实例集中在一个
request.js里导出,业务层只 import 这个实例,禁止再直接 import 默认axios。
2. 请求拦截器忘记 return config
- 现象:请求发不出去,或后端收到的参数、请求头莫名其妙丢失;控制台报
Cannot read properties of undefined。 - 原因:拦截器是 Promise 链上的一环,回调的返回值就是下一个环节的入参,不返回等于把
config丢掉了。 - 修法:成功分支必须
return config;失败分支必须return Promise.reject(error),不要吞掉错误。
3. 响应拦截器里返回了 response.data,业务层还写 res.data
- 现象:
res.data是undefined,页面空白。 - 原因:拦截器已经改写了响应结构,业务层仍按原生结构取值,两处约定不一致。
- 修法:全局统一一种约定(要么返回完整 response,要么返回 data),在拦截器文件顶部写清注释,并用 TS 类型或封装函数固定返回类型。
4. cancel 变量写成模块级共享变量
- 现象:连点两次"取消",把别的请求也取消了;或者没发请求就点取消,报
cancel is not a function。 - 原因:
let cancel = null放在外层作用域,后一次请求的CancelToken回调把前一次的cancel覆盖了。 - 修法:用
axios.CancelToken.source()为每次请求生成独立的source,把它挂在该请求对应的状态里;调用前判空。
5. 用 err.message 字符串匹配判断是否取消
- 现象:换个语言环境或 axios 版本升级后判断失效,取消请求被当成真实错误弹了报错框。
- 原因:取消错误的 message 文案不是稳定 API。
- 修法:统一用
axios.isCancel(err)判断,命中则静默返回,不进入错误提示分支。
6. 拦截器重复注册、从不 eject
- 现象:同一请求弹出多次 loading 或重复携带 token;组件卸载后仍在拦截,内存中拦截器越积越多。
- 原因:
interceptors.request.use()写在组件挂载或函数内部,每次执行都新增一个;eject时也没保存返回的 id。 - 修法:拦截器只在应用入口注册一次;确需动态注册的,用变量保存
use()返回的 id,并在适当时机eject(id)。
7. fetch 链式处理响应体时忘记 return 或忘记二次 await
- 现象:
data拿到的是undefined,或者打印出来是一个 Promise。 - 原因:
res.json()本身返回 Promise,不return或不再await一次,链路没有等待它完成。 - 修法:
fetch(...).then(res => res.json()).then(data => ...),或const res = await fetch(...); const data = await res.json();。
8. 以为 fetch 遇到 404 会走 catch
- 现象:删除一条已不存在的数据,接口返回 404,页面仍提示"删除成功"。
- 原因:
fetch只有网络层失败才 reject,HTTP 4xx/5xx 都算成功响应。 - 修法:拿到响应后先判断
res.ok或res.status,不满足就手动抛错或走失败分支。
练习
练习 1|实例拆分 一个项目里同时对接「业务后端」和「第三方开放平台」两套接口,两套的 baseURL、超时时间、请求头都不同。请用 axios.create 拆出两个实例,各写一个 get 调用,并在浏览器 Network 面板验证两个请求的请求头和地址确实不同。
练习 2|拦截器全链路 给上面某个实例加请求拦截器和响应拦截器:请求拦截器里统一加一个自定义请求头并打印 config.url,响应拦截器里打印状态码。然后在拦截器里写 console.log 观察打印顺序,验证请求拦截器的执行顺序与添加顺序的关系。
练习 3|取消请求 实现一个「搜索框输入即请求」的场景:输入变化就发请求,新请求发出前取消上一次未完成的请求。要求:① 取消时控制台不出现任何错误提示;② 用 axios.isCancel 做判断;③ 人为把后端延迟调大,验证旧请求确实被中断。
练习 4|批量请求 同时请求 3 个不同 id 的详情接口,等全部完成后一次性 console.log 三个结果。再故意把其中一个 id 改成不存在的,观察 catch 里拿到的是什么,并说明为什么拿不到另外两个成功的结果。
练习 5|fetch 对照 用原生 fetch 复现练习 1 中的一个 GET 请求和一个 POST 请求(POST 需带 JSON 请求体并设置 Content-Type),分别用 .then 链和 async/await 各写一遍,比较与 axios 版本在「取响应体」和「错误处理」上的差异,把结论写进自己的笔记。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | vscode配置代码片段 | 07:40 |
| 2 | Fetch发送GET请求 | 22:39 |
| 3 | Fetch发送POST、PUT、DELETE请求 | 13:32 |
| 4 | axios介绍 | 14:00 |
| 5 | axios的引入 | 07:32 |
| 6 | axios基本使用 | 26:51 |
小结
axios.create解决的是配置隔离问题,不是语法糖;一旦项目出现两套以上接口环境,就该拆实例。- 实例和默认
axios是平行关系,混用是配置"不生效"的最常见来源。 - 拦截器的本质是 Promise 链上的一环,返回值即下一环的入参,忘记
return就是主动断链。 - 响应拦截器一旦改写了返回结构,就必须在团队内形成唯一约定,否则每个调用方都要靠猜。
- 取消请求的关键不是
cancel(),而是每次请求持有自己独立的 token,共享变量必然出事故。 - 判断取消错误只认
axios.isCancel,不要依赖 message 文案。 axios.all是「全成功才成功」的语义,一个失败则整体失败,需要部分成功时得自己包一层。fetch更接近底层:响应体要自己读、4xx 要自己判;axios 更接近业务:自动解析、拦截器、超时、取消都现成,工程里优先用 axios,fetch留作理解原理的对照。