前端必会:从 Fetch 到 DeepSeek,一篇搞懂 HTTP 请求的方方面面
你有没有想过,当你在网页上点击一个按钮,数据是怎么从服务器"飞"到你眼前的?今天我们就从最基础的 HTTP 请求讲起,一步步带你理解前后端交互的全过程,最后还会实战调用 DeepSeek 的 API。
一、前端发送 HTTP 请求,有哪几种方式?
在浏览器里,我们主要用两种方式发起 HTTP 请求:
- fetch:现代浏览器原生支持,基于 Promise,语法简洁。
- XMLHttpRequest:老牌选手,AJAX 的基石,但用法相对繁琐。
另外,如果你在使用 OpenAI SDK 之类的封装库,它们底层其实也是基于这些原生方法。不过今天我们把重点放在 fetch 上。
二、这是一种什么编程模式?
2.1 前后端分离
前端负责界面与交互,后端提供数据接口(API)。双方通过 HTTP 请求进行通信,各司其职,大大提升了开发效率和可维护性。
2.2 异步的 async/await
HTTP 请求是"慢操作",如果像普通代码一样从上往下执行,页面就会卡死。所以我们需要异步 。而 async/await 则是异步编程的"语法糖",让我们能用同步的方式写异步代码,清晰又优雅。
2.3 浏览器/服务器架构(B/S)与客户端/服务器架构(C/S)
- B/S 架构:通过浏览器访问 Web 应用,无需安装额外软件。
- C/S 架构:需要安装专门的客户端,比如 Android/iOS 上的 App,它们同样通过 HTTP 与服务器通信。
三、Server 端的基本概念
服务器 = 硬件 + 软件。
- 硬件:一台 24 小时开机的计算机。
- 软件:比如 Java、Node.js 等后端程序,监听某个端口等待请求。
一个典型的本地服务器地址长这样:
http://127.0.0.1:3000
http:通信协议。127.0.0.1:IP 地址,它是网络层用来唯一标识一台服务器的地址。:3000:端口号。一台服务器可以跑多个服务,端口用来区分它们。
那如果我们记不住 IP 怎么办?用域名 啊!比如 www.baidu.com,好记又亲切。浏览器会自动通过 DNS(域名系统)将域名解析成对应的 IP 地址。
四、API Endpoint(端点)
每个 API 都有自己专属的 URL,叫做 endpoint。它是你发起请求的具体"地址"。通常我们会把所有的 endpoint 集中配置管理,方便维护。
五、实战1:Friends 表格渲染(GET 请求)------ 超详细拆解
5.1 整体流程概览(数据流向图)
swift
浏览器加载页面
↓
执行 <script src="main.js"></script>
↓
调用 init() 函数
↓
init() 内部:await loadData()
↓
loadData() 内部:
定义 endpoint = "http://localhost:3000/friends"
执行 await fetch(endpoint) → 等待网络响应
拿到响应对象 res
执行 await res.json() → 把二进制 JSON 转为 JS 数组
return 这个数组
↓
init() 拿到数据,赋值给 friends
↓
init() 调用 renderData(friends)
↓
renderData 内部:
用 document.querySelector 找到 <tbody>
用 map 把每个 friend 对象转成 <tr> 字符串
用 join('') 合并成一个大字符串
用 innerHTML 插入到 <tbody>
↓
页面看到表格
5.2 每个函数/每行代码的详细解释
(1)loadData() 函数
csharp
async function loadData() {
// ① 定义 endpoint(API 的 URL)
const endpoint = 'http://localhost:3000/friends';
// ② await fetch(endpoint)
// - fetch 返回一个 Promise,代表网络请求
// - await 会暂停这个函数的执行,直到 Promise 完成
// - 完成后得到 Response 对象,赋值给 res
const res = await fetch(endpoint);
// ③ await res.json()
// - res.json() 也是一个异步操作,它会读取响应体(二进制 JSON 流)
// - 将其解析为 JavaScript 对象(这里是一个数组)
// - await 再次等待解析完成
const data = await res.json();
// ④ 返回解析后的数据
return data;
}
关键细节:
fetch本身不会抛出 HTTP 错误(如 404),只有网络错误才会抛出。这里没有做res.ok判断,但作为示例可以。res.json()不是魔法:它读取 ReadableStream,然后调用JSON.parse。
(2)renderData(friends) 函数
javascript
function renderData(friends) {
console.log('renderData');
// ① 获取表格的 <tbody> 元素
const oBody = document.querySelector('table tbody');
// ② 判断是否有数据
if (friends.length > 0) {
// ③ friends.map(...)
// - map 遍历数组,对每个 friend 对象返回一个字符串
// - 字符串里嵌入了 friend.id / name / age
// - 注意这里用反引号 `` 包裹多行 HTML
// - map 结果是一个字符串数组,例如:
// ["<tr><td>1</td><td>张三</td><td>20</td></tr>", ...]
oBody.innerHTML = friends.map(function(friend) {
console.log(friend);
return `<tr>
<td>${friend.id}</td>
<td>${friend.name}</td>
<td>${friend.age}</td>
</tr>`;
}).join('');
// ④ .join('') 将字符串数组合并成一个完整的字符串
// 如果不 join,innerHTML 会得到一个数组(被转成逗号分隔的字符串,不是想要的)
}
}
常见误区提醒:
- 如果写成
oBody.innerHTML = friends.map(...)而忘记.join(''),数组会变成"<tr>...,<tr>..."带逗号,表格会错乱。 console.log(friend)用于调试,可以看到每个对象的真实内容。
(3)init() 与启动
scss
async function init() {
console.log('init start');
// 等待 loadData 完成,拿到数据
const friends = await loadData();
console.log(friends);
// 然后才渲染
renderData(friends);
}
// 立即执行 init
init();
// console.log('init end') 这行被注释了,但如果执行,会在 init 完成之前打印,演示异步行为
执行顺序详解:
init()调用后,遇到await loadData(),init会暂停。loadData内部发起网络请求,CPU 去干别的。- 网络响应回来,
loadData继续执行并返回数据,init恢复,执行renderData。 - 因此
console.log('init end')如果写在init()之后,会在renderData之前执行,说明await只阻塞它所在的函数,不阻塞外部。
5.3 HTML 骨架的作用
xml
<main>
<table>
<thead>
<tr><th>id</th><th>name</th><th>age</th></tr>
</thead>
<tbody></tbody> <!-- 这里是动态填充的位置 -->
</table>
</main>
<thead>提前写好表头。<tbody>留空,等着 JS 往里塞数据。
这就是"数据与表现分离":HTML 定义结构,JS 填充内容。
5.4 如何运行这个实战
要真实看到表格,你需要一个后端服务返回 http://localhost:3000/friends。简单的方法:
- 安装 json-server:
npm install -g json-server - 创建
db.json:{ "friends": [{"id":1,"name":"张三","age":20}, ...] } - 运行
json-server --watch db.json --port 3000 - 然后用浏览器打开
index.html,就能看到表格了。
(笔记中虽然没有写这些命令,但提到了"查看 MDN 官方文档",所以可以建议读者去了解如何启动本地服务。)
六、async/await 的执行流程控制(重温)
结合上面的代码,再看一下 async/await 是如何工作的:
await fetch(endpoint):程序会在这里"暂停",直到请求完成,拿到响应对象。await res.json():因为响应体是二进制流,必须转成 JSON 对象才能用。
这两步完成之前,renderData 不会执行------这就是"先等到它请求完数据接口,再渲染"的含义。
七、实战2:调用 DeepSeek 的 API(POST 请求)------ 超详细拆解
7.1 完整请求三要素(再次强调)
| 要素 | 作用 | 代码中的体现 |
|---|---|---|
| 请求行 | 告诉服务器:方法、路径、HTTP 版本 | fetch(endpoint, { method: 'POST' }) |
| 请求头 | 携带元信息,比如内容类型、认证令牌 | headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ...' } |
| 请求体 | 实际要发送的数据(必须是字符串) | body: JSON.stringify(payload) |
7.2 每行代码深度解析
go
// 1. 请求行部分:URL(endpoint)
const endpoint = 'https://api.deepseek.com/chat/completions';
// 2. 请求头部分
const headers = {
'Content-Type': 'application/json',
// api key 通过 Authorization 带上
Authorization: `Bearer ` // 注意:这里需要你填写真实的 API Key
// 格式是 "Bearer sk-xxxxxx"
};
// 3. 请求体部分(payload)
const payload = {
// 便宜点的模型
model: 'deepseek-v4-flash',
messages: [
{ role: 'system', content: 'You are a helpful assistant' },
{ role: 'user', content: '你好, Deepseek' }
]
};
为什么请求体不能直接传对象?
- HTTP 协议传输的是文本(或二进制),不能传输 JavaScript 对象。
fetch的body选项要求传入 String、FormData、Blob、BufferSource 等。- 所以用
JSON.stringify(payload)把对象序列化成 JSON 字符串,比如:'{"model":"deepseek-v4-flash","messages":[...]}'。
javascript
try {
const response = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
const data = await response.json();
console.log(data);
document.getElementById('replay').innerHTML = data.choices[0].message.content;
} catch (error) {
// 网络错误或解析错误会走到这里
}
关键细节:
response.json()和之前一样,把返回的二进制 JSON 转成对象。- DeepSeek 返回的数据结构(根据代码注释)是:
css
{
"choices": [
{ "message": { "content": "你好!很高兴见到你..." } }
]
}
所以用 data.choices[0].message.content 取出回复文本。
然后把这段文本赋值给 id="replay" 的元素的 innerHTML,就会显示在页面上。
7.3 配套 HTML 的作用
xml
<div id="replay"></div>
<script type="module" src="script.js"></script>
<div id="replay">就是一个占位容器,一开始是空的。- 等 AI 返回回复后,
innerHTML会填进去内容。 type="module"是因为script.js里使用了await在顶层(现代浏览器允许在模块中顶层await),如果不加,需要把整个代码包在async函数里。
7.4 运行注意点
- 必须填写有效的
Authorization: Bearer <你的API Key>,否则会返回认证错误。 - 如果跨域?DeepSeek 的 API 支持 CORS,所以可以直接从前端调用(但注意不要在前端暴露 API Key 给用户,实战中只做演示)。
- 建议使用浏览器控制台查看网络请求和返回数据,有助于理解。
八、最终提醒
所有这些 fetch 用法、Response 对象的详细方法,最权威的资料都在 MDN 官方文档。养成查阅 MDN 的习惯,你就能应对任何 HTTP 请求场景。
通过这两个实战的"超详细拆解",你应该能理解:
- 每个函数为什么存在
- 每一行代码在干什么
- 数据如何在前后端之间流动
async/await如何控制执行顺序
现在,自己动手试试看吧!