在 XHR / Promise / async-await 之后,用原生 Fetch 与 Axios 完成 REST 风格 CRUD;配合 json-server 与 transcript 接口联调。
参考:MDN Fetch | Axios 中文文档
目录
- 零、导读与学习价值
- [0.1 案例覆盖清单](#0.1 案例覆盖清单)
- [0.2 核心名词速查](#0.2 核心名词速查)
- [0.5 本章在学什么(知识地图)](#0.5 本章在学什么(知识地图))
- [0.4 建议练习路线](#0.4 建议练习路线)
- [0.6 配套可运行示例(环境自检)](#0.6 配套可运行示例(环境自检))
- [0.3 与前几章的衔接](#0.3 与前几章的衔接)
- [1. HTTP客户端技术演进](#1. HTTP客户端技术演进)
- [1.1 技术发展历程](#1.1 技术发展历程)
- [1.2 技术对比分析](#1.2 技术对比分析)
- [1.3 选择建议](#1.3 选择建议)
- [1.4 配套可运行示例(环境能力检测)](#1.4 配套可运行示例(环境能力检测))
- [2. Fetch API 深度解析](#2. Fetch API 深度解析)
- [2.1 Fetch API 基础](#2.1 Fetch API 基础)
- [2.0 配套可运行示例(最简 GET)](#2.0 配套可运行示例(最简 GET))
- [2.2 Response 对象详解](#2.2 Response 对象详解)
- [2.3 Request 对象配置](#2.3 Request 对象配置)
- [2.4 Fetch 高级功能](#2.4 Fetch 高级功能)
- [2.5 配套示例:03-Fetch 与 REST CRUD](#2.5 配套示例:03-Fetch 与 REST CRUD)
- [2.6 配套可运行示例(response.ok 判断)](#2.6 配套可运行示例(response.ok 判断))
- [3. Axios 完整指南](#3. Axios 完整指南)
- [3.1 Axios 核心概念](#3.1 Axios 核心概念)
- [3.0 配套可运行示例(CDN 引入)](#3.0 配套可运行示例(CDN 引入))
- [3.2 Axios 基础使用](#3.2 Axios 基础使用)
- [3.3 Axios 实例创建](#3.3 Axios 实例创建)
- [3.4 拦截器机制](#3.4 拦截器机制)
- [3.5 请求取消](#3.5 请求取消)
- [3.8 配套示例:04-Axios 案例对照](#3.8 配套示例:04-Axios 案例对照)
- [3.7 配套可运行示例(拦截器最小版)](#3.7 配套可运行示例(拦截器最小版))
- [4. REST API 设计原则](#4. REST API 设计原则)
- [4.1 REST 架构风格](#4.1 REST 架构风格)
- [4.0 配套可运行示例(方法对照表)](#4.0 配套可运行示例(方法对照表))
- [4.2 REST API 设计规范](#4.2 REST API 设计规范)
- [4.3 响应格式规范](#4.3 响应格式规范)
- [4.4 配套:json-server 搭建 REST 练习环境](#4.4 配套:json-server 搭建 REST 练习环境)
- [4.5 配套可运行示例(REST 四方法)](#4.5 配套可运行示例(REST 四方法))
- [5. 请求拦截与响应处理](#5. 请求拦截与响应处理)
- [5.1 统一请求处理](#5.1 统一请求处理)
- [5.2 响应数据转换](#5.2 响应数据转换)
- [5.2 配套可运行示例(响应拦截解包 data)](#5.2 配套可运行示例(响应拦截解包 data))
- [6. 高级功能实现](#6. 高级功能实现)
- [6.1 并发请求控制](#6.1 并发请求控制)
- [6.0 配套可运行示例(Fetch 并行)](#6.0 配套可运行示例(Fetch 并行))
- [6.2 请求重试机制](#6.2 请求重试机制)
- [6.3 缓存策略实现](#6.3 缓存策略实现)
- [6.4 配套可运行示例(axios.all)](#6.4 配套可运行示例(axios.all))
- [7. 生产环境最佳实践](#7. 生产环境最佳实践)
- [7.0 配套可运行示例(区分 axios 错误类型)](#7.0 配套可运行示例(区分 axios 错误类型))
- [7.1 错误监控和日志](#7.1 错误监控和日志)
- [7.2 安全防护措施](#7.2 安全防护措施)
- [7.3 配套可运行示例(统一错误日志)](#7.3 配套可运行示例(统一错误日志))
- [8. 性能优化与监控](#8. 性能优化与监控)
- [8.0 配套可运行示例(AbortController 取消)](#8.0 配套可运行示例(AbortController 取消))
- [8.1 性能优化策略](#8.1 性能优化策略)
- [8.2 监控和告警](#8.2 监控和告警)
- [8.3 配套可运行示例(请求耗时统计)](#8.3 配套可运行示例(请求耗时统计))
- 总结
零、导读与学习价值
0.1 案例覆盖清单
| 目录 / 文件 | 知识点 | 本文章节 |
|---|---|---|
| 01-自定义Promise-Class版 、02-function版 | 手写 Promise 篇 验收(可选复习) | §0.3 |
| 03-Fetch/index.html | GET/POST/PUT/DELETE + await fetch |
§2.5 |
| 04-Axios/01-引入axios.html | CDN / 本地 axios.js |
§3.2 |
| 04/02-axios的基本使用.html | axios(config)、get、await |
§3.3 |
| 04/03-请求配置项.html | url、baseURL、params、data、timeout | §3.4 |
| 04/04-创建axios实例.html | axios.create 多后端 |
§3.5 |
| 04/05-取消请求.html | CancelToken / AbortController | §3.6 |
| 04/06-批量发送请求.html | axios.all + 解构 |
§3.7 |
| 04/07-拦截器.html | 请求/响应拦截、eject |
§5.1 |
| data/transcript.json | json-server REST 数据源 | §4.3 |
配套接口示例:http://shirly.com:8088/transcript(需 hosts 或改用本地 json-server)。
0.2 核心名词速查
| 术语 | 一句话解释 |
|---|---|
| fetch(url, options) | 返回 Promise;默认 GET |
| response.ok | status 在 200~299;404 时 ok 为 false |
| response.json() | 再返回 Promise,解析响应体 |
| axios(config) | 基于 XHR + Promise 的请求库 |
| baseURL | 拼在相对 url 前的公共前缀 |
| 拦截器 | 发请求前 / 进 then 前统一改 config 或 response |
| RESTful | 同一路径用 HTTP 方法区分增删改查 |
0.5 本章在学什么(知识地图)
#mermaid-svg-NnBbRNOpEPnPXe4q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NnBbRNOpEPnPXe4q .error-icon{fill:#552222;}#mermaid-svg-NnBbRNOpEPnPXe4q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NnBbRNOpEPnPXe4q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NnBbRNOpEPnPXe4q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NnBbRNOpEPnPXe4q .marker.cross{stroke:#333333;}#mermaid-svg-NnBbRNOpEPnPXe4q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NnBbRNOpEPnPXe4q p{margin:0;}#mermaid-svg-NnBbRNOpEPnPXe4q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster-label text{fill:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster-label span{color:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster-label span p{background-color:transparent;}#mermaid-svg-NnBbRNOpEPnPXe4q .label text,#mermaid-svg-NnBbRNOpEPnPXe4q span{fill:#333;color:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q .node rect,#mermaid-svg-NnBbRNOpEPnPXe4q .node circle,#mermaid-svg-NnBbRNOpEPnPXe4q .node ellipse,#mermaid-svg-NnBbRNOpEPnPXe4q .node polygon,#mermaid-svg-NnBbRNOpEPnPXe4q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NnBbRNOpEPnPXe4q .rough-node .label text,#mermaid-svg-NnBbRNOpEPnPXe4q .node .label text,#mermaid-svg-NnBbRNOpEPnPXe4q .image-shape .label,#mermaid-svg-NnBbRNOpEPnPXe4q .icon-shape .label{text-anchor:middle;}#mermaid-svg-NnBbRNOpEPnPXe4q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NnBbRNOpEPnPXe4q .rough-node .label,#mermaid-svg-NnBbRNOpEPnPXe4q .node .label,#mermaid-svg-NnBbRNOpEPnPXe4q .image-shape .label,#mermaid-svg-NnBbRNOpEPnPXe4q .icon-shape .label{text-align:center;}#mermaid-svg-NnBbRNOpEPnPXe4q .node.clickable{cursor:pointer;}#mermaid-svg-NnBbRNOpEPnPXe4q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NnBbRNOpEPnPXe4q .arrowheadPath{fill:#333333;}#mermaid-svg-NnBbRNOpEPnPXe4q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NnBbRNOpEPnPXe4q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NnBbRNOpEPnPXe4q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NnBbRNOpEPnPXe4q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NnBbRNOpEPnPXe4q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NnBbRNOpEPnPXe4q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster text{fill:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q .cluster span{color:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NnBbRNOpEPnPXe4q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NnBbRNOpEPnPXe4q rect.text{fill:none;stroke-width:0;}#mermaid-svg-NnBbRNOpEPnPXe4q .icon-shape,#mermaid-svg-NnBbRNOpEPnPXe4q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NnBbRNOpEPnPXe4q .icon-shape p,#mermaid-svg-NnBbRNOpEPnPXe4q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NnBbRNOpEPnPXe4q .icon-shape .label rect,#mermaid-svg-NnBbRNOpEPnPXe4q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NnBbRNOpEPnPXe4q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NnBbRNOpEPnPXe4q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NnBbRNOpEPnPXe4q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 选型 XHR/Fetch/Axios
Fetch REST
Axios 工程化
json-server
拦截器与生产实践
【代码注释】
- 左到右对应 03-Fetch → 04-Axios → data/transcript.json 练习顺序。
0.4 建议练习路线
| 顺序 | 配套 | 验证点 |
|---|---|---|
| ① | 启动 json-server | GET /transcript 有数据 |
| ② | 03-Fetch | GET/POST/PUT/DELETE |
| ③ | 04/02 基本使用 | res.data |
| ④ | 04/04 实例 | 多 baseURL |
| ⑤ | 04/06 批量 | axios.all / Promise.all |
| ⑥ | 04/07 拦截器 | token、统一错误 |
| ⑦ | 04/05 取消 | AbortController |
0.6 配套可运行示例(环境自检)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Fetch 自检</title></head>
<body>
<script>
fetch('https://httpbin.org/get').then(r => r.json()).then(d => console.log('Fetch 可用', d.url));
</script>
</body>
</html>
【代码注释】
- 外网自检无需 json-server;本地 CRUD 再启
json-server跑 03-Fetch。
0.3 与前几章的衔接
- Ajax 基础~进阶篇 :XHR、
ajax()回调。 - Promise 基础~进阶篇 :
ajaxPromise、async/await链。 - 手写 Promise 篇 :理解 Promise 后,Fetch/Axios 的
.then/await更易上手。 - 本篇 :不再手写 XHR ,用 Fetch 或 Axios 对接 REST API(笔记分 Fetch / Axios / RestAPI 三份)。
本地 mock:json-server --watch data/transcript.json --port 8088(见 §4.3)。
1. HTTP客户端技术演进
1.1 技术发展历程
HTTP 客户端技术的发展:
#mermaid-svg-6nD7chmJXNTK9AMs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6nD7chmJXNTK9AMs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6nD7chmJXNTK9AMs .error-icon{fill:#552222;}#mermaid-svg-6nD7chmJXNTK9AMs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6nD7chmJXNTK9AMs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6nD7chmJXNTK9AMs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6nD7chmJXNTK9AMs .marker.cross{stroke:#333333;}#mermaid-svg-6nD7chmJXNTK9AMs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6nD7chmJXNTK9AMs p{margin:0;}#mermaid-svg-6nD7chmJXNTK9AMs .edge{stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .section--1 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section--1 path,#mermaid-svg-6nD7chmJXNTK9AMs .section--1 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section--1 text{fill:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth--1{stroke-width:17;}#mermaid-svg-6nD7chmJXNTK9AMs .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-0 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-0 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-0 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-0 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-0{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-0{stroke-width:14;}#mermaid-svg-6nD7chmJXNTK9AMs .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-1 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-1 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-1 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-1 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-1{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-1{stroke-width:11;}#mermaid-svg-6nD7chmJXNTK9AMs .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-2 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-2 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-2 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-2 text{fill:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-2{stroke-width:8;}#mermaid-svg-6nD7chmJXNTK9AMs .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-3 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-3 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-3 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-3 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-3{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-3{stroke-width:5;}#mermaid-svg-6nD7chmJXNTK9AMs .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-4 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-4 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-4 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-4 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-4{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-4{stroke-width:2;}#mermaid-svg-6nD7chmJXNTK9AMs .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-5 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-5 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-5 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-5 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-5{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-5{stroke-width:-1;}#mermaid-svg-6nD7chmJXNTK9AMs .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-6 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-6 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-6 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-6 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-6{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-6{stroke-width:-4;}#mermaid-svg-6nD7chmJXNTK9AMs .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-7 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-7 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-7 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-7 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-7{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-7{stroke-width:-7;}#mermaid-svg-6nD7chmJXNTK9AMs .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-8 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-8 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-8 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-8 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-8{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-8{stroke-width:-10;}#mermaid-svg-6nD7chmJXNTK9AMs .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-9 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-9 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-9 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-9 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-9{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-9{stroke-width:-13;}#mermaid-svg-6nD7chmJXNTK9AMs .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-10 rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-10 path,#mermaid-svg-6nD7chmJXNTK9AMs .section-10 circle,#mermaid-svg-6nD7chmJXNTK9AMs .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-10 text{fill:black;}#mermaid-svg-6nD7chmJXNTK9AMs .node-icon-10{font-size:40px;color:black;}#mermaid-svg-6nD7chmJXNTK9AMs .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .edge-depth-10{stroke-width:-16;}#mermaid-svg-6nD7chmJXNTK9AMs .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-6nD7chmJXNTK9AMs .lineWrapper line{stroke:black;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled,#mermaid-svg-6nD7chmJXNTK9AMs .disabled circle,#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:lightgray;}#mermaid-svg-6nD7chmJXNTK9AMs .disabled text{fill:#efefef;}#mermaid-svg-6nD7chmJXNTK9AMs .section-root rect,#mermaid-svg-6nD7chmJXNTK9AMs .section-root path,#mermaid-svg-6nD7chmJXNTK9AMs .section-root circle{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-6nD7chmJXNTK9AMs .section-root text{fill:#ffffff;}#mermaid-svg-6nD7chmJXNTK9AMs .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-6nD7chmJXNTK9AMs .edge{fill:none;}#mermaid-svg-6nD7chmJXNTK9AMs .eventWrapper{filter:brightness(120%);}#mermaid-svg-6nD7chmJXNTK9AMs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 早期阶段 1999 XMLHttpRequest 出现 2005 Ajax 概念正式提出 2006 jQuery Ajax 简化开发 标准化阶段 2008 XHR Level 2 规范 2014 Fetch API 规范发布 2015 Axios 库发布 现代阶段 2017 Axios 成为主流选择 2020 原生 Fetch 广泛支持 2025 现代HTTP客户端生态 HTTP 客户端技术演进
【代码注释】
- XHR → Fetch 标准化 → Axios 生态的时间线概览。
- 现代项目仍可能维护 legacy XHR 代码。
- Axios 底层仍是 XHR,不是 Fetch API。
- 选型表见 §1.2。
1.2 技术对比分析
| 特性 | XMLHttpRequest | Fetch API | Axios |
|---|---|---|---|
| API 设计 | 回调函数 | Promise 链 | Promise 链 |
| 浏览器支持 | 所有浏览器 | 现代浏览器 | 需要引入库 |
| 请求拦截 | 复杂实现 | 需要手动封装 | 内置支持 |
| 响应转换 | 手动解析 | 手动解析 | 自动转换 |
| 取消请求 | abort() 方法 | AbortController | CancelToken |
| 进度监控 | 原生支持 | 有限支持 | 原生支持 |
| 超时控制 | 手动实现 | 需要配合 AbortController | 内置支持 |
| Node.js 支持 | 需要适配器 | 原生支持 | 原生支持 |
1.3 选择建议
根据场景选择HTTP客户端:
使用 Fetch API 的场景:
- 现代浏览器环境
- 简单的 HTTP 请求
- 不需要复杂的请求拦截
- 希望减少依赖库
使用 Axios 的场景:
- 需要请求/响应拦截器
- 需要请求取消功能
- 需要自动的 JSON 数据转换
- 需要同时支持浏览器和 Node.js
- 需要更完善的错误处理
使用 XMLHttpRequest 的场景:
- 需要支持老版本浏览器
- 需要上传进度监控
- 维护现有代码库
【实战要点】
- 新项目浏览器端优先 Fetch 或封装 Axios;需上传进度、极老 IE 再考虑 XHR。
- Axios 本质是 XHR,不是 Fetch;拦截器、超时、JSON 转换是其优势。
【面试考点】
Q1:Fetch 与 XHR、Axios 对比?
A:XHR 回调+进度;Fetch 原生 Promise 需判 ok;Axios 封装 XHR+拦截器。
Q2:Fetch 为何说「HTTP 错误不 reject」?
A:只有网络级失败 reject;4xx/5xx 仍 resolve Response,需看 ok。
1.4 配套可运行示例(环境能力检测)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>客户端 API</title></head>
<body>
<script>
console.log({ XHR: typeof XMLHttpRequest, fetch: typeof fetch });
</script>
</body>
</html>
【代码注释】
- 浏览器内置 XHR/fetch;Axios 需单独引入(见 §3.0)。
【本章小结】
| 技术 | 特点 |
|---|---|
| XHR | 回调、进度、老项目 |
| Fetch | 原生 Promise、需判 ok |
| Axios | 拦截器、超时、res.data |
2. Fetch API 深度解析
2.1 Fetch API 基础
2.0 配套可运行示例(最简 GET)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>最简 fetch</title></head>
<body>
<script>
fetch('https://httpbin.org/get')
.then(r => r.json())
.then(d => console.log(d));
</script>
</body>
</html>
【代码注释】
- 两步:
fetch→json();生产环境应对r.ok做判断。
Fetch API 核心概念:
javascript
// Fetch 基础语法
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('获取数据:', data);
})
.catch(error => {
console.error('请求失败:', error);
});
// 完整的 Fetch 请求
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: '张三',
age: 28
}),
mode: 'cors',
credentials: 'include',
cache: 'no-cache',
redirect: 'follow'
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
【代码注释】
- Fetch 经典陷阱:网络错误才 reject,HTTP 4xx/5xx 仍 resolve。
- 必须
if (!response.ok) throw或判断 status。 - 第二参数配置 method、headers、body、signal 等。
- 等价 Promise 基础篇 的 Promise 链式请求。
2.2 Response 对象详解
Response 对象属性和方法:
javascript
async function analyzeResponse() {
const response = await fetch('https://api.example.com/data');
// 响应状态信息
console.log('状态码:', response.status); // 200
console.log('状态文本:', response.statusText); // "OK"
console.log('是否成功:', response.ok); // true
console.log('响应URL:', response.url); // 实际请求的URL
console.log('重定向:', response.redirected); // 是否重定向
console.log('响应类型:', response.type); // basic, cors, opaque
// 响应头信息
console.log('Content-Type:', response.headers.get('Content-Type'));
console.log('Content-Length:', response.headers.get('Content-Length'));
// 遍历所有响应头
for (const [key, value] of response.headers.entries()) {
console.log(`${key}: ${value}`);
}
// 响应体处理方法
if (response.headers.get('Content-Type').includes('application/json')) {
const jsonData = await response.json(); // 解析JSON
console.log('JSON数据:', jsonData);
} else if (response.headers.get('Content-Type').includes('text/')) {
const textData = await response.text(); // 获取文本
console.log('文本数据:', textData);
} else if (response.headers.get('Content-Type').includes('image/')) {
const blobData = await response.blob(); // 获取二进制数据
const imageUrl = URL.createObjectURL(blobData);
console.log('图片URL:', imageUrl);
}
// 原始响应体
const arrayBuffer = await response.arrayBuffer();
console.log('ArrayBuffer:', arrayBuffer);
}
【代码注释】
- Response 体只能消费一次;json() 后再 text() 会报错。
headers.get不区分大小写;遍历用 entries()。- blob/arrayBuffer 用于文件、图片下载。
response.type在跨域 no-cors 时为 opaque。
2.3 Request 对象配置
完整的 Request 配置选项:
javascript
const request = new Request('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value'
},
body: JSON.stringify({ key: 'value' }),
mode: 'cors', // cors, no-cors, same-origin, navigate
credentials: 'include', // include, same-origin, omit
cache: 'default', // default, no-store, reload, no-cache, force-cache
redirect: 'follow', // manual, follow, error
referrer: 'no-referrer', // no-referrer, client
referrerPolicy: 'no-referrer-when-downgrade',
integrity: 'sha256-abcdef1234567890', // SRI (Subresource Integrity)
keepalive: false, // 保持连接
signal: abortController.signal // 取消信号
});
// 使用 Request 对象
fetch(request)
.then(response => response.json())
.then(data => console.log(data));
【代码注释】
- 结合本节标题与 0.1 案例表对照学习。
- Fetch 注意 ok/status;Axios 注意 data 字段。
- 本地先 json-server 再跑 HTML。
- 跨域与 Cookie 参考 Ajax 进阶与跨域篇、会话控制篇 Session。
2.4 Fetch 高级功能
请求取消:
javascript
// 使用 AbortController 取消请求
const abortController = new AbortController();
const fetchPromise = fetch('https://api.example.com/slow-endpoint', {
signal: abortController.signal
});
// 设置超时取消
const timeoutId = setTimeout(() => {
abortController.abort();
console.log('请求超时,已取消');
}, 5000);
fetchPromise
.then(response => {
clearTimeout(timeoutId);
return response.json();
})
.then(data => {
console.log('数据:', data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('请求失败:', error);
}
});
【代码注释】
- 现代取消标准;Axios v0.22+ 支持
signal。 - abort 后 fetch reject DOMException。
- 超时 = race 或 setTimeout+abort。
- 旧 Axios 用 CancelToken(05-取消请求.html)。
流式数据处理:
javascript
// 处理流式响应
async function fetchStreamData() {
const response = await fetch('https://api.example.com/large-data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
let receivedLength = 0;
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('流式数据接收完成');
break;
}
const chunk = decoder.decode(value, { stream: true });
result += chunk;
receivedLength += value.length;
console.log(`接收到 ${value.length} 字节,总计 ${receivedLength} 字节`);
console.log('当前数据块:', chunk);
// 实时处理数据
processChunk(chunk);
}
return result;
}
// 分块传输数据处理
async function fetchChunkedData() {
const response = await fetch('https://api.example.com/chunked-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 按行分割处理
const lines = buffer.split('\n');
buffer = lines.pop(); // 保留不完整的行
for (const line of lines) {
if (line.trim()) {
try {
const data = JSON.parse(line);
console.log('接收到数据对象:', data);
// 处理每个数据对象
} catch (error) {
console.error('JSON解析错误:', error);
}
}
}
}
}
【代码注释】
- 流式读取大响应;SSE/分块 JSON 场景。
- Fetch 的 body 是 ReadableStream。
- Axios 也有 onDownloadProgress(XHR 能力)。
- 配套未展开,作进阶了解。
2.5 配套示例:03-Fetch 与 REST CRUD
03-Fetch/index.html 对 transcript 资源做四种方法(与 REST API 笔记 笔记一致):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Fetch REST</title></head>
<body>
<button id="get">GET</button>
<button id="post">POST</button>
<script>
const BASE = 'http://127.0.0.1:8088/transcript'; // json-server 或 hosts 映射的 shirly.com
document.querySelector('#get').onclick = async () => {
const res = await fetch(BASE);
const data = await res.json();
console.log(data);
};
document.querySelector('#post').onclick = async () => {
const res = await fetch(BASE, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '测试', age: 20 })
});
console.log(await res.json());
};
</script>
</body>
</html>
【代码注释】
fetch返回 Promise,解析后为 Response 对象;必须 再await res.json()才得到数据对象。- PUT
transcript/19、DELETEtranscript/18见完整案例;路径中带 id 表资源主键。 - 网络失败进
catch;HTTP 4xx/5xx 默认仍 resolve ,需判断res.ok或res.status。 - 可用
async/await或.then(res => res.json())两种风格(配套三种写法都有)。
REST 服务 fetch 页面按钮 REST 服务 fetch 页面按钮 #mermaid-svg-v9JwPs5tcgEp8O6K{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v9JwPs5tcgEp8O6K .error-icon{fill:#552222;}#mermaid-svg-v9JwPs5tcgEp8O6K .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v9JwPs5tcgEp8O6K .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v9JwPs5tcgEp8O6K .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v9JwPs5tcgEp8O6K .marker.cross{stroke:#333333;}#mermaid-svg-v9JwPs5tcgEp8O6K svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v9JwPs5tcgEp8O6K p{margin:0;}#mermaid-svg-v9JwPs5tcgEp8O6K .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v9JwPs5tcgEp8O6K text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v9JwPs5tcgEp8O6K .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-v9JwPs5tcgEp8O6K .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-v9JwPs5tcgEp8O6K #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-v9JwPs5tcgEp8O6K .sequenceNumber{fill:white;}#mermaid-svg-v9JwPs5tcgEp8O6K #sequencenumber{fill:#333;}#mermaid-svg-v9JwPs5tcgEp8O6K #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-v9JwPs5tcgEp8O6K .messageText{fill:#333;stroke:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v9JwPs5tcgEp8O6K .labelText,#mermaid-svg-v9JwPs5tcgEp8O6K .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .loopText,#mermaid-svg-v9JwPs5tcgEp8O6K .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v9JwPs5tcgEp8O6K .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-v9JwPs5tcgEp8O6K .noteText,#mermaid-svg-v9JwPs5tcgEp8O6K .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-v9JwPs5tcgEp8O6K .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v9JwPs5tcgEp8O6K .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v9JwPs5tcgEp8O6K .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v9JwPs5tcgEp8O6K .actorPopupMenu{position:absolute;}#mermaid-svg-v9JwPs5tcgEp8O6K .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-v9JwPs5tcgEp8O6K .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v9JwPs5tcgEp8O6K .actor-man circle,#mermaid-svg-v9JwPs5tcgEp8O6K line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-v9JwPs5tcgEp8O6K :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} fetch(url, {method, headers, body}) HTTP 请求 Response res.json() → 业务数据
【代码注释】
- POST/PUT 的
body与Content-Type必须匹配(JSON 用application/json)。 - 跨域时服务端需 CORS(Ajax 进阶与跨域篇);Fetch 支持
credentials: 'include'带 Cookie。 - 取消请求用
AbortController+signal(§2.4),与 Axios 的 signal 概念一致。
【实战要点】
- 统一封装:
async function request(url, options) { const res = await fetch(url, options); if (!res.ok) throw new Error(res.status); return res.json(); } - 不要对同一
response多次调用json(),body 只能读一次。
【面试考点】
Q1:fetch 在什么情况下会进入 catch?
A:网络错误、CORS 失败、手动 throw、Abort 取消等;非 2xx 默认不进 catch。
Q2:response.ok 与 response.status 的关系?
A:ok 为 status 在 200~299 时为 true。
2.6 配套可运行示例(response.ok 判断)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Fetch ok</title></head>
<body>
<script>
fetch('https://httpbin.org/status/404')
.then(res => {
console.log('status', res.status, 'ok', res.ok);
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.catch(e => console.error(e.message));
</script>
</body>
</html>
【代码注释】
- 404 时 fetch 仍 resolve ,必须看
res.ok或status;与 Axios 默认 reject 不同。
【本章小结】
| API | 要点 |
|---|---|
| fetch | 返回 Promise,再 json() |
| res.json() | 再一层 Promise,body 只读一次 |
| AbortController | 取消请求 |
3. Axios 完整指南
3.1 Axios 核心概念
3.0 配套可运行示例(CDN 引入)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>引入 axios</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
console.log('axios', typeof axios);
axios.get('https://httpbin.org/get').then(r => console.log(r.data.url));
</script>
</body>
</html>
【代码注释】
- 对应 01-引入axios.html ;本地案例可用
./axios/axios.js。
Axios 特性详解:
javascript
// Axios 核心特性
const axiosFeatures = {
// 1. 支持 Promise API
promiseAPI: true,
// 2. 拦截器支持
interceptors: true,
// 3. 请求取消
requestCancellation: true,
// 4. 自动 JSON 数据转换
jsonTransform: true,
// 5. 客户端防止 XSRF
xsrfProtection: true,
// 6. 并发请求支持
concurrentRequests: true,
// 7. 超时设置
timeout: true,
// 8. 浏览器和 Node.js 支持
crossPlatform: true
};
console.log('Axios 特性:', axiosFeatures);
【代码注释】
- Axios 基于 XHR + Promise;浏览器/Node 均可。
- GET 查询用
params对象;POST 体用data。 - 响应在
response.data,状态在response.status。 - 01-引入axios.html 引入脚本后即可用全局
axios。
3.2 Axios 基础使用
安装和引入:
bash
# 使用 npm 安装
npm install axios
# 使用 yarn 安装
yarn add axios
# 使用 CDN
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
【代码注释】
- npm 安装 axios;浏览器可用 CDN 或本地 axios.js。
- Node 项目
require('axios')或 ESM import。 - 版本与课堂 bundled axios 可能略有 API 差异(CancelToken 等)。
- 生产锁定 package 版本。
基础请求方法:
javascript
// ES6 模块方式
import axios from 'axios';
// CommonJS 方式
const axios = require('axios');
// GET 请求
axios.get('/user?id=12345')
.then(response => {
console.log('用户数据:', response.data);
})
.catch(error => {
console.error('获取失败:', error);
});
// 带参数的 GET 请求
axios.get('/user', {
params: {
id: 12345,
name: '张三'
}
})
.then(response => console.log(response.data));
// POST 请求
axios.post('/user', {
name: '李四',
age: 28,
email: 'lisi@example.com'
})
.then(response => console.log('新用户:', response.data))
.catch(error => console.error('创建失败:', error));
// 多种请求方式
axios.put('/user/12345', { name: '王五' });
axios.patch('/user/12345', { age: 29 });
axios.delete('/user/12345');
【代码注释】
- Axios 基于 XHR + Promise;浏览器/Node 均可。
- GET 查询用
params对象;POST 体用data。 - 响应在
response.data,状态在response.status。 - 01-引入axios.html 引入脚本后即可用全局
axios。
3.3 Axios 实例创建
自定义实例:
javascript
// 创建自定义实例
const instance = axios.create({
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'custom-value'
}
});
// 使用自定义实例
instance.get('/users')
.then(response => console.log(response.data))
.catch(error => console.error(error));
// 创建多个实例用于不同用途
const apiInstance = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000
});
const authInstance = axios.create({
baseURL: 'https://auth.example.com',
timeout: 5000,
headers: {
'Authorization': 'Bearer token123'
}
});
// 为不同实例配置不同的拦截器
apiInstance.interceptors.request.use(config => {
// API 请求的特定处理
return config;
});
authInstance.interceptors.request.use(config => {
// 认证请求的特定处理
return config;
});
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
3.4 拦截器机制
请求拦截器:
javascript
// 添加请求拦截器
axios.interceptors.request.use(
config => {
// 在发送请求之前做些什么
console.log('发送请求:', config.method.toUpperCase(), config.url);
// 添加认证令牌
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加时间戳防止缓存
if (config.method === 'get') {
config.params = {
...config.params,
_t: Date.now()
};
}
// 显示加载指示器
showLoadingIndicator();
return config;
},
error => {
// 对请求错误做些什么
console.error('请求错误:', error);
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
response => {
// 对响应数据做点什么
console.log('接收响应:', response.status, response.config.url);
// 隐藏加载指示器
hideLoadingIndicator();
// 统一处理响应数据格式
if (response.data.code === 0) {
return response.data.data;
} else {
return Promise.reject(new Error(response.data.message));
}
},
error => {
// 对响应错误做点什么
console.error('响应错误:', error);
// 隐藏加载指示器
hideLoadingIndicator();
// 处理特定错误状态码
if (error.response) {
switch (error.response.status) {
case 401:
console.error('未授权,请重新登录');
redirectToLogin();
break;
case 403:
console.error('权限不足');
break;
case 404:
console.error('请求的资源不存在');
break;
case 500:
console.error('服务器错误');
break;
default:
console.error('请求失败:', error.response.status);
}
} else if (error.request) {
console.error('网络错误,请检查网络连接');
} else {
console.error('请求配置错误:', error.message);
}
return Promise.reject(error);
}
);
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
3.5 请求取消
取消请求实现:
javascript
// 使用 CancelToken
const source = axios.CancelToken.source();
axios.get('/api/data', {
cancelToken: source.token
})
.then(response => {
console.log('数据:', response.data);
})
.catch(error => {
if (axios.isCancel(error)) {
console.log('请求被取消:', error.message);
} else {
console.error('请求失败:', error);
}
});
// 取消请求
source.cancel('操作被用户取消');
// 使用 AbortController
const controller = new AbortController();
axios.get('/api/data', {
signal: controller.signal
})
.then(response => console.log(response.data))
.catch(error => {
if (error.name === 'CanceledError') {
console.log('请求被取消');
}
});
// 取消请求
controller.abort();
【代码注释】
- 05-取消请求.html ;
cancel()触发,catch 里axios.isCancel(err)。 - 新版推荐 AbortController + signal 统一 fetch/axios。
- 路由切换时应取消未完成请求,避免 setState 已卸载组件。
- 重复点击提交前 cancel 上一次。
3.8 配套示例:04-Axios 案例对照
引入(01-引入axios.html):
html
<script src="./axios/axios.js"></script>
<!-- 或 CDN: https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js -->
【代码注释】
- 浏览器直接打开;需后端或 json-server 已启动。
- 与 0.1 表中同名案例对照。
- BASE URL 改为你本机
127.0.0.1:8088即可。 - 失败时看 Network 面板状态码与 CORS。
基本使用(02-axios的基本使用.html):
javascript
// ① 配置对象
axios({ method: 'GET', url: 'http://127.0.0.1:8088/transcript' })
.then(res => console.log(res));
// ② url + config(baseURL 拼相对路径)
axios('/transcript/2', { method: 'get', baseURL: 'http://127.0.0.1:8088', timeout: 5000 });
// ③ 别名
axios.get('/transcript/4', { baseURL: 'http://127.0.0.1:8088' });
// ④ async/await
const res = await axios.get('/transcript/5', { baseURL: 'http://127.0.0.1:8088' });
console.log(res.data); // 业务数据在 data,不是 xhr.response
【代码注释】
res为 axios 响应对象 :data、status、headers、config(笔记 §5)。- 失败进
catch或 try/catch;Axios 对 非 2xx 默认 reject(与 Fetch 不同)。 - 03-请求配置项.html 演示
params(查询串)、data(请求体)、timeout等。
创建实例(04-创建axios实例.html):
javascript
const musicInstance = axios.create({
baseURL: 'http://api.fuming.site:54255',
timeout: 5000
});
const transcriptInstance = axios.create({
baseURL: 'http://127.0.0.1:8088',
timeout: 2000
});
musicInstance.get('/playlist/detail', { params: { id: 3779629 } })
.then(res => console.log(res.data));
【代码注释】
- 不同业务域用不同实例,避免每次写完整 URL;实例无
all/CancelToken静态方法,用axios.all或从默认 axios 引入。 params自动序列化为 URL 查询;data用于 POST/PUT/PATCH 体。
批量请求(06-批量发送请求.html):
javascript
axios.defaults.baseURL = 'http://127.0.0.1:8088';
const r1 = axios.get('/transcript/1');
const r2 = axios.get('/transcript/2');
axios.all([r1, r2, r3, r4])
.then(([s1, s2, s3, s4]) => { console.log(s1.data, s2.data); });
// 旧写法:.then(axios.spread((s1,s2,s3,s4)=>{...}))
【代码注释】
- 等价
Promise.all;任一失败整体进 catch。 - 与 Promise 进阶篇
Promise.all并行拉歌单场景同一思路。
拦截器(07-拦截器.html) 见 §5;05-取消请求.html 见 §3.6 CancelToken。
【实战要点】
- 项目入口
axios.create({ baseURL })+ 拦截器挂 token、统一错误提示。 - 读响应用
res.data,不要和 Fetch 的response.json()混淆。
【面试考点】
Q1:axios 响应结构有哪些字段?
A:data、status、statusText、headers、config。
Q2:axios.create 与 axios.defaults 区别?
A:create 新实例隔离配置;defaults 改全局默认。
Q3:axios 与 fetch 在错误处理上的差异?
A:axios 非 2xx 默认 reject;fetch 需判 ok。
3.7 配套可运行示例(拦截器最小版)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>拦截器</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.defaults.baseURL = 'http://127.0.0.1:8088';
axios.interceptors.request.use(cfg => {
cfg.headers['X-Demo'] = '1';
return cfg;
});
axios.interceptors.response.use(res => res.data);
axios.get('/transcript/1').then(d => console.log('data', d));
</script>
</body>
</html>
【代码注释】
- 对应 07-拦截器.html ;须先
json-server;响应拦截直接返回data简化业务代码。
【本章小结】
| 能力 | API |
|---|---|
| 实例 | axios.create |
| 批量 | axios.all |
| 取消 | CancelToken / signal |
| 拦截 | interceptors.request/response |
4. REST API 设计原则
4.1 REST 架构风格
4.0 配套可运行示例(方法对照表)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>REST 方法</title></head>
<body>
<pre id="out"></pre>
<script>
const rows = [
['GET', '查询列表/单条'],
['POST', '新增'],
['PUT', '全量更新'],
['PATCH', '部分更新'],
['DELETE', '删除']
];
out.textContent = rows.map(r => r[0].padEnd(6) + r[1]).join('\n');
</script>
</body>
</html>
【代码注释】
- 03-Fetch 对
transcript演示 GET/POST/PUT/DELETE;PATCH 视后端支持。
REST (Representational State Transfer) 是一种软件架构风格,用于设计网络应用的API。
核心原则:
javascript
// REST API 设计原则
const restPrinciples = {
// 1. 统一接口
uniformInterface: {
description: '使用统一的资源标识符和接口',
example: '/api/users/123'
},
// 2. 无状态
stateless: {
description: '每个请求都包含所有必要信息',
example: '请求头包含认证信息'
},
// 3. 可缓存
cacheable: {
description: '响应应该明确是否可缓存',
example: 'Cache-Control: max-age=3600'
},
// 4. 客户端-服务器分离
clientServer: {
description: '客户端和服务器职责分离',
example: '前端负责UI,后端负责数据处理'
},
// 5. 分层系统
layeredSystem: {
description: '允许中间层(代理、负载均衡)',
example: 'CDN、API网关'
},
// 6. 按需代码(可选)
codeOnDemand: {
description: '服务器可以临时扩展客户端功能',
example: 'JavaScript代码传输'
}
};
【代码注释】
- REST 是架构风格,非标准协议;实践中有各种变体。
- URL 用名词复数、层级不宜过深。
- 与 §4.4 json-server 路由对照记忆。
- 业务 code 包装见 §4.3 响应格式(扩展)。
4.2 REST API 设计规范
URL 设计规范:
javascript
// REST API URL 设计
const apiDesign = {
// 基础 URL
baseUrl: 'https://api.example.com/v1',
// 资源命名(使用名词复数)
resources: {
users: '/users', // 用户资源
articles: '/articles', // 文章资源
comments: '/comments' // 评论资源
},
// 层级关系
hierarchy: {
userArticles: '/users/{userId}/articles',
articleComments: '/articles/{articleId}/comments'
},
// 过滤和排序
filtering: {
base: '/articles',
filters: {
status: '/articles?status=published',
date: '/articles?created_after=2024-01-01',
sort: '/articles?sort=-created_at',
pagination: '/articles?page=1&limit=20'
}
},
// HTTP 方法映射
methods: {
collection: {
list: 'GET /articles', // 获取列表
create: 'POST /articles' // 创建资源
},
resource: {
get: 'GET /articles/{id}', // 获取单个资源
update: 'PUT /articles/{id}', // 更新资源
patch: 'PATCH /articles/{id}', // 部分更新
delete: 'DELETE /articles/{id}' // 删除资源
}
}
};
// 实际使用示例
const apiExamples = {
// 获取用户列表
getUsers: 'GET /api/v1/users?page=1&limit=20',
// 获取特定用户
getUser: 'GET /api/v1/users/123',
// 创建用户
createUser: 'POST /api/v1/users',
// 更新用户
updateUser: 'PUT /api/v1/users/123',
// 删除用户
deleteUser: 'DELETE /api/v1/users/123',
// 获取用户的文章
getUserArticles: 'GET /api/v1/users/123/articles',
// 搜索文章
searchArticles: 'GET /api/v1/articles?q=keyword&status=published'
};
【代码注释】
- REST 是架构风格,非标准协议;实践中有各种变体。
- URL 用名词复数、层级不宜过深。
- 与 §4.4 json-server 路由对照记忆。
- 业务 code 包装见 §4.3 响应格式(扩展)。
4.3 响应格式规范
标准响应格式:
javascript
// 成功响应
{
"code": 0,
"message": "success",
"data": {
"id": 123,
"name": "张三",
"email": "zhangsan@example.com"
},
"timestamp": "2024-01-15T10:30:00Z"
}
// 错误响应
{
"code": 1001,
"message": "用户不存在",
"error": {
"type": "NotFoundError",
"details": "找不到ID为123的用户"
},
"timestamp": "2024-01-15T10:30:00Z"
}
// 分页响应
{
"code": 0,
"message": "success",
"data": {
"items": [
{ "id": 1, "name": "项目1" },
{ "id": 2, "name": "项目2" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
},
"timestamp": "2024-01-15T10:30:00Z"
}
【代码注释】
- 结合本节标题与 0.1 案例表对照学习。
- Fetch 注意 ok/status;Axios 注意 data 字段。
- 本地先 json-server 再跑 HTML。
- 跨域与 Cookie 参考 Ajax 进阶与跨域篇、会话控制篇 Session。
4.4 配套:json-server 搭建 REST 练习环境
REST API 笔记 笔记:用 json-server 快速提供 REST 接口,供 03-Fetch / 04-Axios 联调。
bash
npm install -g json-server
cd 本篇-Fetch&Axios
json-server --watch data/transcript.json --port 8088 --host 127.0.0.1 --delay 500
【代码注释】
- JSON 根必须是对象 (如
{ "transcript": [...] }),不能是数组。 --delay模拟网络延迟;--watch文件改动后接口数据同步更新。- 资源路径:
GET/POST /transcript,GET/PUT/PATCH/DELETE /transcript/:id(与 03-Fetch / 04-Axios 中 URL 一致)。
| 操作 | 方法 | 示例 |
|---|---|---|
| 列表 | GET | http://127.0.0.1:8088/transcript |
| 单条 | GET | http://127.0.0.1:8088/transcript/2 |
| 新增 | POST | 带 JSON 请求体 |
| 全量改 | PUT | /transcript/2 + body |
| 删除 | DELETE | /transcript/2 |
非 RESTful vs RESTful(笔记对比):
非 REST:POST /news/create、GET /news/delete?id=1 (URL 动作化)
RESTful:POST /news、DELETE /news/1 (URL 表资源,方法表动作)
【代码注释】
- 笔记示例用
shirly.com:8088时可在 hosts 映射到127.0.0.1,或直接把案例里的域名改为127.0.0.1:8088。 - 与业务接口
{ code, msg, data }包装不同,json-server 直接返回 JSON 数组/对象。
【实战要点】
- 前后端分离联调:先 json-server,再换真实后端;Axios
baseURL一处修改即可。 - 用 Postman / Apipost 先测通接口再写前端。
【面试考点】
Q1:RESTful 的核心是什么?
A:URL 表资源,HTTP 方法表动作,而非 URL 里写 create/delete。
Q2:POST 与 GET 在 Fetch/Axios 配置上的区别?
A:GET 用 params/查询串;POST 用 method+body+Content-Type。
4.5 配套可运行示例(REST 四方法)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>REST 速览</title></head>
<body>
<button id="g">GET</button><button id="d">DELETE id=1</button>
<script>
const B = 'http://127.0.0.1:8088/transcript';
document.getElementById('g').onclick = async () => console.log(await (await fetch(B)).json());
document.getElementById('d').onclick = async () => console.log(await (await fetch(B+'/1',{method:'DELETE'})).json());
</script>
</body>
</html>
【代码注释】
- 与 03-Fetch/index.html 同源;PUT/PATCH 见完整案例。
【本章小结】
| 原则 | 说明 |
|---|---|
| 资源 | URL 表名词,动词用 HTTP 方法 |
| json-server | 快速 mock REST |
| 响应 | 列表数组或单条对象 |
5. 请求拦截与响应处理
5.1 统一请求处理
HTTP 客户端封装:
javascript
class HttpClient {
constructor(config = {}) {
this.instance = axios.create({
baseURL: config.baseURL || '/api',
timeout: config.timeout || 10000,
headers: {
'Content-Type': 'application/json'
}
});
this.setupInterceptors();
}
setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
config => {
// 添加认证令牌
const token = this.getAuthToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 添加请求ID用于追踪
config.headers['X-Request-ID'] = this.generateRequestId();
// 请求日志
console.log(`[${config.method.toUpperCase()}] ${config.url}`);
return config;
},
error => {
console.error('请求错误:', error);
return Promise.reject(error);
}
);
// 响应拦截器
this.instance.interceptors.response.use(
response => {
// 统一处理响应数据
const { data } = response;
// 处理业务状态码
if (data.code === 0) {
return data.data;
} else {
return Promise.reject(new Error(data.message));
}
},
error => {
// 统一错误处理
return this.handleError(error);
}
);
}
getAuthToken() {
return localStorage.getItem('auth_token');
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
handleError(error) {
if (error.response) {
// 服务器响应错误
const { status, data } = error.response;
switch (status) {
case 401:
this.handleAuthError();
break;
case 403:
console.error('权限不足');
break;
case 404:
console.error('资源不存在');
break;
case 500:
console.error('服务器错误');
break;
default:
console.error('请求失败:', data.message);
}
} else if (error.request) {
// 网络错误
console.error('网络错误,请检查连接');
} else {
// 请求配置错误
console.error('请求配置错误:', error.message);
}
return Promise.reject(error);
}
handleAuthError() {
console.error('认证失败,请重新登录');
localStorage.removeItem('auth_token');
// 跳转到登录页
window.location.href = '/login';
}
// 快捷方法
get(url, params, config = {}) {
return this.instance.get(url, { ...config, params });
}
post(url, data, config = {}) {
return this.instance.post(url, data, config);
}
put(url, data, config = {}) {
return this.instance.put(url, data, config);
}
delete(url, config = {}) {
return this.instance.delete(url, config);
}
patch(url, data, config = {}) {
return this.instance.patch(url, data, config);
}
}
// 使用示例
const httpClient = new HttpClient({
baseURL: 'https://api.example.com',
timeout: 15000
});
// 发送请求
async function fetchData() {
try {
const users = await httpClient.get('/users', { page: 1, limit: 20 });
console.log('用户列表:', users);
} catch (error) {
console.error('获取用户失败:', error);
}
}
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
5.2 响应数据转换
数据转换处理:
javascript
// 响应数据转换器
class ResponseTransformer {
// 转换用户数据
static transformUser(data) {
return {
id: data._id || data.id,
username: data.username || data.name,
email: data.email,
avatar: data.avatar || data.profile?.avatar,
role: data.role || 'user',
createdAt: data.created_at || data.createdAt,
updatedAt: data.updated_at || data.updatedAt
};
}
// 批量转换用户数据
static transformUserList(data) {
return {
items: data.items?.map(item => this.transformUser(item)) || [],
pagination: {
page: data.page || data.pagination?.page || 1,
limit: data.limit || data.pagination?.limit || 20,
total: data.total || data.pagination?.total || 0,
totalPages: data.total_pages || data.pagination?.totalPages || 0
}
};
}
// 转换文章数据
static transformArticle(data) {
return {
id: data._id || data.id,
title: data.title,
content: data.content,
excerpt: data.excerpt || data.summary,
author: this.transformUser(data.author),
tags: data.tags || [],
category: data.category,
status: data.status || 'draft',
views: data.views || 0,
createdAt: data.created_at || data.createdAt,
updatedAt: data.updated_at || data.updatedAt
};
}
// 通用错误转换
static transformError(error) {
return {
code: error.code || error.response?.data?.code || 500,
message: error.message || error.response?.data?.message || '未知错误',
details: error.details || error.response?.data?.details || null,
timestamp: new Date().toISOString()
};
}
}
// 使用转换器
class ApiClient {
constructor(httpClient) {
this.http = httpClient;
}
async getUsers(params) {
try {
const rawData = await this.http.get('/users', params);
return ResponseTransformer.transformUserList(rawData);
} catch (error) {
throw ResponseTransformer.transformError(error);
}
}
async getUser(id) {
try {
const rawData = await this.http.get(`/users/${id}`);
return ResponseTransformer.transformUser(rawData);
} catch (error) {
throw ResponseTransformer.transformError(error);
}
}
async getArticles(params) {
try {
const rawData = await this.http.get('/articles', params);
return {
items: rawData.items?.map(item =>
ResponseTransformer.transformArticle(item)
) || [],
pagination: rawData.pagination
};
} catch (error) {
throw ResponseTransformer.transformError(error);
}
}
}
【代码注释】
- 结合本节标题与 0.1 案例表对照学习。
- Fetch 注意 ok/status;Axios 注意 data 字段。
- 本地先 json-server 再跑 HTML。
- 跨域与 Cookie 见 Ajax 进阶篇、会话控制篇。
5.2 配套可运行示例(响应拦截解包 data)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>响应拦截</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.defaults.baseURL = 'http://127.0.0.1:8088';
axios.interceptors.response.use(res => {
if (res.data && typeof res.data === 'object') return res.data;
return res;
});
axios.get('/transcript').then(d => console.log('业务数据', d));
</script>
</body>
</html>
【代码注释】
- 对应 07-拦截器.html 响应分支;业务层直接拿到列表数组,无需再写
.data。
【本章小结】
| 环节 | 作用 |
|---|---|
| 请求拦截 | 加 token、改 header |
| 响应拦截 | 解包 data、统一错误 |
【实战要点】
- 请求拦截必须
return config;勿在拦截器里抛错而不 reject。 - 多个拦截器按注册顺序执行。
【面试考点】
Q1:axios 拦截器执行顺序?
A:请求拦截后注册先执行;响应拦截先注册先执行。
6. 高级功能实现
6.1 并发请求控制
6.0 配套可运行示例(Fetch 并行)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>并行 fetch</title></head>
<body>
<script>
Promise.all([
fetch('https://httpbin.org/get?n=1').then(r => r.json()),
fetch('https://httpbin.org/get?n=2').then(r => r.json())
]).then(arr => console.log(arr.map(x => x.args.n)));
</script>
</body>
</html>
【代码注释】
- 与 06-批量发送请求.html 思路一致;任一失败整体 reject。
请求队列管理:
javascript
class RequestQueue {
constructor(concurrency = 5) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
add(requestFn) {
return new Promise((resolve, reject) => {
const task = {
requestFn,
resolve,
reject
};
this.queue.push(task);
this.run();
});
}
async run() {
while (this.running < this.concurrency && this.queue.length > 0) {
const task = this.queue.shift();
this.running++;
try {
const result = await task.requestFn();
task.resolve(result);
} catch (error) {
task.reject(error);
} finally {
this.running--;
this.run();
}
}
}
}
// 使用示例
const queue = new RequestQueue(3); // 最多同时3个请求
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3',
'https://api.example.com/data4',
'https://api.example.com/data5'
];
const promises = urls.map(url =>
queue.add(() => axios.get(url))
);
Promise.all(promises)
.then(responses => console.log('所有请求完成:', responses))
.catch(error => console.error('有请求失败:', error));
【代码注释】
- 结合本节标题与 0.1 案例表对照学习。
- Fetch 注意 ok/status;Axios 注意 data 字段。
- 本地先 json-server 再跑 HTML。
- 跨域与 Cookie 参考 Ajax 进阶与跨域篇、会话控制篇 Session。
6.2 请求重试机制
智能重试策略:
javascript
class RetryableRequest {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.retryDelay = options.retryDelay || 1000;
this.backoffMultiplier = options.backoffMultiplier || 2;
this.retryCondition = options.retryCondition || this.defaultRetryCondition;
}
defaultRetryCondition(error) {
// 默认重试条件
if (!error.response) {
// 网络错误重试
return true;
}
const status = error.response.status;
// 5xx 错误重试,4xx 错误不重试
return status >= 500 && status < 600;
}
async execute(requestFn) {
let lastError;
let delay = this.retryDelay;
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
const result = await requestFn();
if (attempt > 0) {
console.log(`重试成功,尝试次数: ${attempt}`);
}
return result;
} catch (error) {
lastError = error;
if (attempt < this.maxRetries && this.retryCondition(error)) {
console.log(`第 ${attempt + 1} 次尝试失败,${delay}ms 后重试`);
await this.sleep(delay);
delay *= this.backoffMultiplier;
} else {
break;
}
}
}
throw new Error(`请求失败,已尝试 ${this.maxRetries + 1} 次: ${lastError.message}`);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用示例
const retryable = new RetryableRequest({
maxRetries: 5,
retryDelay: 1000,
backoffMultiplier: 1.5
});
async function fetchWithRetry(url) {
return retryable.execute(async () => {
const response = await axios.get(url);
return response.data;
});
}
// 实际应用
fetchWithRetry('https://api.example.com/unstable-endpoint')
.then(data => console.log('数据:', data))
.catch(error => console.error('最终失败:', error));
【代码注释】
- 仅对幂等请求或安全错误重试;POST 慎重重试。
- 指数退避减轻服务端压力。
- 与 axios 拦截器结合可统一重试。
- 生产配合网关限流。
6.3 缓存策略实现
HTTP 缓存封装:
javascript
class CachedHttpClient {
constructor(options = {}) {
this.http = options.http || axios;
this.cache = options.cache || new Map();
this.defaultTTL = options.defaultTTL || 60000; // 默认1分钟
}
async get(url, params, options = {}) {
const cacheKey = this.buildCacheKey('GET', url, params);
const cached = this.cache.get(cacheKey);
// 检查缓存
if (cached && !this.isCacheExpired(cached)) {
console.log('使用缓存数据:', cacheKey);
return cached.data;
}
// 发送请求
try {
const response = await this.http.get(url, { params });
const data = response.data;
// 存入缓存
if (options.cache !== false) {
const ttl = options.ttl || this.defaultTTL;
this.cache.set(cacheKey, {
data,
expiresAt: Date.now() + ttl
});
}
return data;
} catch (error) {
// 如果请求失败且有缓存数据,返回缓存
if (cached && options.fallbackToCache) {
console.log('请求失败,使用缓存数据');
return cached.data;
}
throw error;
}
}
buildCacheKey(method, url, params) {
const paramsStr = JSON.stringify(params || {});
return `${method}:${url}:${paramsStr}`;
}
isCacheExpired(cached) {
return cached.expiresAt < Date.now();
}
clear() {
this.cache.clear();
}
clearPattern(pattern) {
for (const key of this.cache.keys()) {
if (key.includes(pattern)) {
this.cache.delete(key);
}
}
}
}
// 使用示例
const cachedClient = new CachedHttpClient({
http: axios,
defaultTTL: 30000 // 30秒缓存
});
// 第一次请求会发送网络请求
cachedClient.get('/api/users', { page: 1 })
.then(data => console.log('用户数据:', data));
// 第二次请求会使用缓存
cachedClient.get('/api/users', { page: 1 })
.then(data => console.log('用户数据(缓存):', data));
// 清除所有缓存
cachedClient.clear();
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
6.4 配套可运行示例(axios.all)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>批量请求</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.defaults.baseURL = 'http://127.0.0.1:8088';
Promise.all([1,2].map(id => axios.get('/transcript/'+id)))
.then(arr => console.log(arr.map(r => r.data)));
</script>
</body>
</html>
【代码注释】
- 对应 06-批量发送请求.html ;也可用原生
axios.all(旧 API)。
【本章小结】
| 能力 | 场景 |
|---|---|
| 并发队列 | 限流防打爆 |
| 重试 | 弱网补偿 |
| 缓存 | 减少重复 GET |
【实战要点】
- 批量用
Promise.all时注意单点失败;需要部分成功用allSettled。 - 重试加指数退避,避免雪崩。
【面试考点】
Q1:如何实现请求并发上限?
A:维护队列 + running 计数,完成后再取下一个任务。
7. 生产环境最佳实践
7.0 配套可运行示例(区分 axios 错误类型)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>错误类型</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.get('https://httpbin.org/status/500').catch(e => {
console.log('有 response', !!e.response, 'status', e.response?.status);
});
axios.get('https://invalid.invalid').catch(e => {
console.log('无 response', !e.response, 'message', e.message);
});
</script>
</body>
</html>
【代码注释】
error.response存在表示服务器有响应;仅有error.request多为 DNS/网络问题。
7.1 错误监控和日志
全面的错误监控系统:
javascript
class ApiMonitor {
constructor(httpClient) {
this.http = httpClient;
this.setupMonitoring();
}
setupMonitoring() {
// 监控请求开始
this.http.interceptors.request.use(config => {
config.metadata = {
startTime: Date.now(),
requestId: this.generateRequestId()
};
this.logRequest(config);
return config;
});
// 监控响应
this.http.interceptors.response.use(
response => {
this.logResponse(response);
this.trackPerformance(response.config);
return response;
},
error => {
this.logError(error);
this.trackError(error);
return Promise.reject(error);
}
);
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
logRequest(config) {
const logData = {
requestId: config.metadata.requestId,
method: config.method.toUpperCase(),
url: config.url,
headers: this.sanitizeHeaders(config.headers),
params: config.params,
data: config.data,
timestamp: new Date().toISOString()
};
console.log('🚀 API 请求:', logData);
// 发送到日志系统
this.sendToLogSystem('request', logData);
}
logResponse(response) {
const duration = Date.now() - response.config.metadata.startTime;
const logData = {
requestId: response.config.metadata.requestId,
status: response.status,
duration: duration,
size: JSON.stringify(response.data).length,
timestamp: new Date().toISOString()
};
console.log('✅ API 响应:', logData);
// 发送到日志系统
this.sendToLogSystem('response', logData);
}
logError(error) {
const logData = {
requestId: error.config?.metadata?.requestId,
message: error.message,
code: error.code,
status: error.response?.status,
url: error.config?.url,
timestamp: new Date().toISOString()
};
console.error('❌ API 错误:', logData);
// 发送到错误跟踪系统
this.sendToErrorTracking(logData);
}
trackPerformance(config) {
const duration = Date.now() - config.metadata.startTime;
// 记录性能指标
if (duration > 3000) {
console.warn('⚠️ 慢请求告警:', {
url: config.url,
duration: duration
});
}
// 发送到性能监控系统
this.sendToPerformanceMonitor({
url: config.url,
method: config.method,
duration: duration,
timestamp: Date.now()
});
}
trackError(error) {
// 发送到错误监控系统
this.sendToErrorTracking({
type: 'api_error',
message: error.message,
stack: error.stack,
url: error.config?.url,
timestamp: Date.now()
});
}
sanitizeHeaders(headers) {
const sanitized = { ...headers };
// 移除敏感信息
const sensitiveKeys = ['authorization', 'token', 'cookie'];
sensitiveKeys.forEach(key => {
delete sanitized[key];
});
return sanitized;
}
sendToLogSystem(type, data) {
// 实现发送到日志系统的逻辑
// 例如: sendToSplunk(data), sendToELK(data) 等
}
sendToErrorTracking(error) {
// 实现发送到错误跟踪系统的逻辑
// 例如: sendToSentry(error), sendToBugsnag(error) 等
}
sendToPerformanceMonitor(metric) {
// 实现发送到性能监控系统的逻辑
// 例如: sendToDataDog(metric), sendToNewRelic(metric) 等
}
}
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
7.2 安全防护措施
API 安全最佳实践:
javascript
class SecureHttpClient {
constructor(options = {}) {
this.http = options.http || axios.create();
this.securityConfig = options.security || {};
this.setupSecurity();
}
setupSecurity() {
// 请求安全拦截器
this.http.interceptors.request.use(config => {
// 添加 CSRF 令牌
const csrfToken = this.getCSRFToken();
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken;
}
// 添加请求签名
if (this.securityConfig.enableSignature) {
config.headers['X-Signature'] = this.generateRequestSignature(config);
}
// 验证请求大小
const requestSize = this.calculateRequestSize(config);
if (requestSize > this.securityConfig.maxRequestSize) {
throw new Error('请求大小超过限制');
}
// 添加时间戳防止重放攻击
config.headers['X-Timestamp'] = Date.now();
return config;
});
// 响应安全验证
this.http.interceptors.response.use(
response => {
// 验证响应签名
if (this.securityConfig.enableSignature) {
this.validateResponseSignature(response);
}
// 检查安全响应头
this.checkSecurityHeaders(response);
return response;
},
error => {
// 处理安全相关错误
this.handleSecurityError(error);
return Promise.reject(error);
}
);
}
getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]')?.content;
}
generateRequestSignature(config) {
// 实现请求签名逻辑
const secret = this.securityConfig.signatureSecret;
const payload = {
method: config.method,
url: config.url,
params: config.params,
timestamp: Date.now()
};
return this.signPayload(payload, secret);
}
signPayload(payload, secret) {
const crypto = require('crypto');
const payloadStr = JSON.stringify(payload);
return crypto.createHmac('sha256', secret).update(payloadStr).digest('hex');
}
validateResponseSignature(response) {
// 实现响应签名验证逻辑
const receivedSignature = response.headers['x-signature'];
if (!receivedSignature) {
throw new Error('缺少响应签名');
}
// 验证签名...
}
checkSecurityHeaders(response) {
// 检查安全相关的响应头
const securityHeaders = [
'X-Content-Type-Options',
'X-Frame-Options',
'X-XSS-Protection',
'Strict-Transport-Security'
];
securityHeaders.forEach(header => {
if (!response.headers[header]) {
console.warn(`缺少安全响应头: ${header}`);
}
});
}
calculateRequestSize(config) {
let size = 0;
if (config.data) {
size += JSON.stringify(config.data).length;
}
if (config.params) {
size += JSON.stringify(config.params).length;
}
return size;
}
handleSecurityError(error) {
// 处理安全相关错误
if (error.response?.status === 403) {
console.error('权限被拒绝');
// 可能的 CSRF 攻击
} else if (error.response?.status === 419) {
console.error('CSRF 令牌过期');
// 刷新 CSRF 令牌
this.refreshCSRFToken();
}
}
refreshCSRFToken() {
// 实现刷新 CSRF 令牌的逻辑
}
}
【代码注释】
- Response 体只能消费一次;json() 后再 text() 会报错。
headers.get不区分大小写;遍历用 entries()。- blob/arrayBuffer 用于文件、图片下载。
response.type在跨域 no-cors 时为 opaque。
7.3 配套可运行示例(统一错误日志)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>错误处理</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
axios.interceptors.response.use(
r => r,
err => { console.error('[API]', err.response?.status, err.message); return Promise.reject(err); }
);
axios.get('/not-exist').catch(() => {});
</script>
</body>
</html>
【代码注释】
- 生产可对接 Sentry;区分
error.response/error.request/ 配置错误。
【本章小结】
| 主题 | 要点 |
|---|---|
| 监控 | 耗时、状态码、错误率 |
| 安全 | HTTPS、token、CSRF |
【实战要点】
- 勿在前端日志打印完整 token;敏感字段脱敏。
- CORS、CSP 与接口鉴权配合。
【面试考点】
Q1:axios 错误对象如何区分网络错与 4xx?
A:有 error.response 为服务端响应;仅有 error.request 多为网络/超时。
8. 性能优化与监控
8.0 配套可运行示例(AbortController 取消)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>取消 fetch</title></head>
<body>
<button id="go">请求</button><button id="cancel">取消</button>
<script>
const c = new AbortController();
document.getElementById('go').onclick = () => {
fetch('https://httpbin.org/delay/5', { signal: c.signal })
.catch(e => console.log(e.name));
};
document.getElementById('cancel').onclick = () => c.abort();
</script>
</body>
</html>
【代码注释】
- 对应 05-取消请求.html 的 Abort 方案;路由离开页面前应 abort 未完成请求。
8.1 性能优化策略
HTTP 客户端性能优化:
javascript
class PerformanceOptimizedClient {
constructor(options = {}) {
this.http = options.http || axios.create();
this.performanceConfig = options.performance || {};
this.setupOptimizations();
}
setupOptimizations() {
// 启用请求压缩
if (this.performanceConfig.enableCompression) {
this.setupCompression();
}
// 启用连接复用
if (this.performanceConfig.enableKeepAlive) {
this.setupKeepAlive();
}
// 启用请求批处理
if (this.performanceConfig.enableBatching) {
this.setupBatching();
}
}
setupCompression() {
this.http.interceptors.request.use(config => {
// 启用请求压缩
config.headers['Accept-Encoding'] = 'gzip, deflate, br';
return config;
});
}
setupKeepAlive() {
// 配置 HTTP keep-alive
this.http.defaults.headers.common['Connection'] = 'keep-alive';
}
setupBatching() {
this.requestQueue = [];
this.batchTimeout = null;
this.http.interceptors.request.use(config => {
return new Promise((resolve) => {
this.requestQueue.push({ config, resolve });
if (!this.batchTimeout) {
this.batchTimeout = setTimeout(() => {
this.flushBatch();
}, this.performanceConfig.batchDelay || 100);
}
});
});
}
flushBatch() {
const batch = this.requestQueue.splice(0, this.performanceConfig.batchSize || 10);
// 批量处理请求
batch.forEach(({ config, resolve }) => {
resolve(config);
});
this.batchTimeout = null;
}
// 预加载资源
async preloadResources(urls) {
const preloadPromises = urls.map(url =>
this.http.head(url).catch(error => {
console.warn(`预加载失败: ${url}`, error);
})
);
await Promise.all(preloadPromises);
}
// 资源优先级管理
prioritizeRequest(config, priority) {
config.priority = priority;
config.metadata = {
...config.metadata,
priority
};
return config;
}
}
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
8.2 监控和告警
实时监控系统:
javascript
class ApiMonitoringSystem {
constructor() {
this.metrics = {
requests: {
total: 0,
success: 0,
error: 0,
timeout: 0
},
performance: {
totalTime: 0,
avgTime: 0,
maxTime: 0,
minTime: Infinity
},
endpoints: {}
};
this.alerts = [];
this.thresholds = {
errorRate: 0.05, // 5% 错误率
avgResponseTime: 2000, // 2秒平均响应时间
timeoutRate: 0.01 // 1% 超时率
};
}
recordRequest(config, duration, success) {
// 更新总体指标
this.metrics.requests.total++;
if (success) {
this.metrics.requests.success++;
} else {
this.metrics.requests.error++;
}
// 更新性能指标
this.updatePerformanceMetrics(duration);
// 更新端点指标
this.updateEndpointMetrics(config.url, duration, success);
// 检查告警条件
this.checkAlerts();
}
updatePerformanceMetrics(duration) {
const perf = this.metrics.performance;
perf.totalTime += duration;
perf.avgTime = perf.totalTime / this.metrics.requests.total;
perf.maxTime = Math.max(perf.maxTime, duration);
perf.minTime = Math.min(perf.minTime, duration);
}
updateEndpointMetrics(url, duration, success) {
if (!this.metrics.endpoints[url]) {
this.metrics.endpoints[url] = {
requests: 0,
totalTime: 0,
errors: 0,
avgTime: 0
};
}
const endpoint = this.metrics.endpoints[url];
endpoint.requests++;
endpoint.totalTime += duration;
endpoint.avgTime = endpoint.totalTime / endpoint.requests;
if (!success) {
endpoint.errors++;
}
}
checkAlerts() {
const { requests, performance } = this.metrics;
// 检查错误率
const errorRate = requests.error / requests.total;
if (errorRate > this.thresholds.errorRate) {
this.triggerAlert('HIGH_ERROR_RATE', {
currentRate: errorRate,
threshold: this.thresholds.errorRate
});
}
// 检查平均响应时间
if (performance.avgTime > this.thresholds.avgResponseTime) {
this.triggerAlert('SLOW_RESPONSE', {
currentAvg: performance.avgTime,
threshold: this.thresholds.avgResponseTime
});
}
}
triggerAlert(type, details) {
const alert = {
type,
details,
timestamp: new Date().toISOString()
};
this.alerts.push(alert);
// 发送告警通知
this.sendAlertNotification(alert);
}
sendAlertNotification(alert) {
// 实现告警通知逻辑
console.warn('🚨 API 告警:', alert);
// 可以集成各种通知渠道
// - Slack
// - Email
// - 短信
// - PagerDuty
}
getMetrics() {
return {
...this.metrics,
calculated: {
errorRate: this.metrics.requests.error / this.metrics.requests.total,
successRate: this.metrics.requests.success / this.metrics.requests.total
}
};
}
getEndpointMetrics(url) {
return this.metrics.endpoints[url] || null;
}
reset() {
this.metrics = {
requests: { total: 0, success: 0, error: 0, timeout: 0 },
performance: { totalTime: 0, avgTime: 0, maxTime: 0, minTime: Infinity },
endpoints: {}
};
this.alerts = [];
}
}
// 使用示例
const monitoring = new ApiMonitoringSystem();
// 集成到 HTTP 客户端
const monitoredClient = axios.create();
monitoredClient.interceptors.request.use(config => {
config.metadata = { startTime: Date.now() };
return config;
});
monitoredClient.interceptors.response.use(
response => {
const duration = Date.now() - response.config.metadata.startTime;
monitoring.recordRequest(response.config, duration, true);
return response;
},
error => {
const duration = Date.now() - error.config.metadata.startTime;
monitoring.recordRequest(error.config, duration, false);
return Promise.reject(error);
}
);
// 获取监控指标
setInterval(() => {
const metrics = monitoring.getMetrics();
console.log('API 监控指标:', metrics);
}, 60000); // 每分钟输出一次
【代码注释】
- 07-拦截器.html :请求拦截改 config,须
return config放行。 - 响应拦截改 res 或统一解包
data;return res传给 then。 eject(id)移除拦截器;多个拦截器按注册顺序执行。- 流程:请求拦截2→1→发请求→响应拦截→回调(笔记)。
8.3 配套可运行示例(请求耗时统计)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>耗时</title></head>
<body>
<script>
async function timedFetch(url) {
const t0 = performance.now();
const res = await fetch(url);
await res.json();
console.log(url, (performance.now() - t0).toFixed(0) + 'ms');
}
timedFetch('https://httpbin.org/delay/1');
</script>
</body>
</html>
【代码注释】
- 结合 Performance API;Axios 可在拦截器记录
config.metadata.start。
【本章小结】
| 优化 | 手段 |
|---|---|
| 减少请求 | 合并、缓存、防抖 |
| 体验 | 骨架屏、取消无用请求 |
【实战要点】
- 列表页离开路由时 abort 未完成请求。
- 静态资源走 CDN,接口走独立域名便于监控。
【面试考点】
Q1:如何监控前端 API 性能?
A:拦截器记时 + 上报;PerformanceResourceTiming 看 DNS/TTFB。
总结
高频面试题速查
| 题 | 要点 |
|---|---|
| Fetch vs Axios 错误 | Fetch 看 ok;Axios 非 2xx reject |
| res.json() | 再一层 Promise,body 只读一次 |
| axios 拦截器 | 必须 return config/res |
| REST | 资源+HTTP 动词 |
| axios.create | 隔离 baseURL/timeout |
| 取消请求 | AbortController / CancelToken |
#mermaid-svg-uPwvCN2M5eu5r5iZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uPwvCN2M5eu5r5iZ .error-icon{fill:#552222;}#mermaid-svg-uPwvCN2M5eu5r5iZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uPwvCN2M5eu5r5iZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uPwvCN2M5eu5r5iZ .marker.cross{stroke:#333333;}#mermaid-svg-uPwvCN2M5eu5r5iZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uPwvCN2M5eu5r5iZ p{margin:0;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge{stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 text{fill:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth--1{stroke-width:17;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-0{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-0{stroke-width:14;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-1{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-1{stroke-width:11;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 text{fill:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-2{stroke-width:8;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-3{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-3{stroke-width:5;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-4{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-4{stroke-width:2;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-5{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-5{stroke-width:-1;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-6{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-6{stroke-width:-4;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-7{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-7{stroke-width:-7;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-8{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-8{stroke-width:-10;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-9{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-9{stroke-width:-13;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 polygon,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 text{fill:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .node-icon-10{font-size:40px;color:black;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge-depth-10{stroke-width:-16;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:lightgray;}#mermaid-svg-uPwvCN2M5eu5r5iZ .disabled text{fill:#efefef;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root rect,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root path,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root circle,#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root text{fill:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-root span{color:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .section-2 span{color:#ffffff;}#mermaid-svg-uPwvCN2M5eu5r5iZ .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-uPwvCN2M5eu5r5iZ .edge{fill:none;}#mermaid-svg-uPwvCN2M5eu5r5iZ .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-uPwvCN2M5eu5r5iZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本篇 HTTP 客户端
Fetch
Promise Response
res.ok 判断
REST CRUD
Axios
create 实例
拦截器
all 批量
REST
json-server
方法+资源路径
衔接
Ajax 基础篇 XHR
Promise
手写 Promise 篇 手写
【代码注释】
- 本篇 主线:Fetch REST + Axios 工程化 + json-server。
- 练习顺序见总结;与 Promise 系列 Promise 衔接。
- 生产项目多用 Axios 或自封装 Fetch。
- 配套路径以 03-Fetch、04-Axios 为准。
知识主线:
- 选型:简单场景 Fetch;要拦截器、超时、自动 JSON 用 Axios。
- Fetch :
fetch → json();4xx 不自动 reject ,需response.ok。 - Axios :
res.data;create多后端;all并行;拦截器改 config/响应。 - REST :
03-Fetch/04-Axios对transcript做 GET/POST/PUT/DELETE;本地 json-server 搭环境。 - 工程:§5~§8 封装类、重试、监控属生产扩展,与配套 HTML 案例互补。
建议练习顺序 :json-server 启动 → 03-Fetch 四按钮 → 02-axios基本使用 → 04-创建实例 → 06-批量 → 07-拦截器 → 05-取消请求。
相关资源: