无论你是刚入门的前端新手,还是正在探索 AI 应用开发的工程师,理解 HTTP 请求都是绕不开的基本功。本文将通过一个完整的 Demo 项目,从最基础的本地数据请求讲起,一步步带你掌握从前端发送 HTTP 请求、理解前后端分离架构,到最终调用大模型 API 的完整链路。
一、项目概览:三个模块,一条链路
整个 Demo 项目由三个模块组成,恰好覆盖了前端 HTTP 请求的三大典型场景:
css
demo1/
├── backend/ ← 后端:用 json-server 快速搭建 REST API
├── frontend/ ← 前端:从后端获取数据并渲染到页面
└── demo/ ← AI实战:调用 DeepSeek 大模型 API
| 模块 | 角色 | 核心能力 |
|---|---|---|
| backend | 数据提供方 | 提供 http://127.0.0.1:3000/friends 接口 |
| frontend | 数据消费方 | fetch 获取数据 → 渲染 HTML 表格 |
| demo | AI 接口调用 | fetch POST 调用大模型,拿到 AI 回复 |
下面我们逐个拆解,从最简单的开始。
二、Backend:三分钟搭建一个后端服务
很多人觉得"后端"很神秘,其实一个能用的后端,三分钟就能搭好。
2.1 数据准备
json
// data.json --- 这就是我们的"数据库"
{
"friends": [
{ "id": 1, "name": "张三", "age": 18 },
{ "id": 2, "name": "李四", "age": 20 }
]
}
一个 JSON 文件,两个好友对象,简单到不能再简单。
2.2 一行命令启动服务
json
// package.json
{
"scripts": {
"dev": "json-server --watch data.json --port 3000"
}
}
json-server 是一个零配置的 REST API 工具,它做了什么?
- 读取
data.json文件 - 自动生成 RESTful 接口------
GET /friends返回好友列表 - 监听 3000 端口,等待请求
一条 npm run dev,你的后端就上线了。访问 http://127.0.0.1:3000/friends,浏览器直接返回 JSON 数据。
💡 核心认知:后端本质上就是一个"等着被问话的程序"。前端问一句(发请求),后端答一句(返回数据)。
三、服务器到底是什么?
说起"服务器",别被这个词吓到。拆开看,就两层意思:
3.1 硬件层面
服务器本质上就是一台一直开着的电脑,在网络中有一个唯一的 IP 地址,负责接收和处理请求。
3.2 软件层面
服务器上运行着特定的软件来响应请求:
- Node.js + json-server:Demo 中用的方案,轻量快速
- Java(Spring Boot):企业级后端常用
- Nginx:高性能 Web 服务器,常用于反向代理
3.3 IP、端口、域名、DNS
当你访问 http://127.0.0.1:3000/friends,这个地址每个部分都有含义:
arduino
http://127.0.0.1:3000/friends
│ │ │ │
协议 IP地址 端口 路径(endpoint)
- IP 地址 :网络世界中服务器的唯一标识。
127.0.0.1(也叫 localhost)永远指向你自己的电脑 - 端口号 :一台服务器上可能跑着多个服务,端口号就是用来区分它们的。
:3000表示"我要找 3000 端口上的那个服务"。常见端口:80(HTTP)、443(HTTPS)、8080(Java)、3000(前端开发) - 域名 :
www.baidu.com比110.242.68.66好记一万倍,域名就是 IP 的"别名" - DNS :把域名翻译成 IP 地址的"翻译官"。你输入
www.baidu.com,DNS 帮你查出背后的 IP,然后浏览器才能找到服务器
四、Frontend:从数据获取到页面渲染
有了后端,前端怎么拿数据?来看 frontend 模块的完整代码。
4.1 页面骨架
html
<!-- frontend/index.html -->
<header>
<h1>前端发送 HTTP 请求</h1>
</header>
<main>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
</tr>
</thead>
<tbody></tbody>
</table>
</main>
<script src="./main.js"></script>
一个标题 + 一张空表格,表格内容留给 JavaScript 动态填充。这就是数据驱动视图的雏形------HTML 只定义结构,数据由 JS 负责填入。
4.2 核心逻辑:fetch + 渲染
javascript
// frontend/main.js
let friends = [];
// 第一步:从后端获取数据
async function loadData() {
const endpoint = 'http://127.0.0.1:3000/friends';
const res = await fetch(endpoint);
const data = await res.json();
console.log(data);
return data;
}
// 第二步:把数据渲染成表格行
function renderData(friends) {
const oBody = document.querySelector('table tbody');
if (friends.length > 0) {
oBody.innerHTML = friends.map(function (friend) {
return `
<tr>
<td>${friend.id}</td>
<td>${friend.name}</td>
<td>${friend.age}</td>
</tr>
`;
}).join('');
}
}
// 第三步:编排整个流程
async function init() {
console.log('init start');
const friends = await loadData(); // 等数据回来
console.log(friends);
renderData(friends); // 数据到了,渲染
}
init();
三个函数,职责分明:
| 函数 | 职责 | 模式 |
|---|---|---|
loadData() |
数据获取 | 纯 I/O,只管拿数据 |
renderData() |
视图渲染 | 数据 → DOM |
init() |
流程编排 | 串联前两步,控制执行顺序 |
这就是关注点分离(Separation of Concerns)------每个函数只做一件事,组合起来完成复杂任务。
4.3 数据转换的核心:map + join
javascript
friends.map(function (friend) {
return `<tr>...</tr>`;
}).join('');
这条链式调用干了什么?
map:遍历数组中的每个friend对象,把它转换 成一行<tr>HTML 字符串。[{id:1,name:'张三'}, ...]→['<tr>...张三...</tr>', '<tr>...李四...</tr>']join(''):把字符串数组拼接成一个大字符串。['<tr>...</tr>', '<tr>...</tr>']→'<tr>...</tr><tr>...</tr>'innerHTML =:一次性写入 DOM,浏览器渲染出两行表格
📌 这其实就是 React / Vue 虚拟 DOM diff 之前最原始的渲染方式------数据变了,重新生成 HTML,整体替换。理解了这一点,就理解了为什么现代框架需要虚拟 DOM 来优化性能。
五、HTTP 请求的完整结构
上面 loadData() 里的 fetch(endpoint) 是一个最简单的 GET 请求。但一个完整的 HTTP 请求,其实由三部分组成:
bash
┌──────────────────────────────────────────┐
│ HTTP 请求报文 │
├────────────┬─────────────────────────────┤
│ 请求行 │ POST /chat/completions │
│ │ HTTP/1.1 │
├────────────┼─────────────────────────────┤
│ 请求头 │ Content-Type: app/json │
│ │ Authorization: Bearer xxx │
├────────────┼─────────────────────────────┤
│ 请求体 │ {"model":"...","messages":} │
└────────────┴─────────────────────────────┘
5.1 请求行
方法 + URL + HTTP版本,告诉服务器"我要干什么":
GET------ 拿数据POST------ 提交数据PUT------ 更新数据DELETE------ 删除数据
5.2 请求头
附加的元信息,告诉服务器"我是谁""我能接收什么":
Content-Type: application/json------ 我发的数据是 JSON 格式Authorization: Bearer sk-xxx------ 这是我的身份凭证(API Key)
5.3 请求体
实际要发送的数据。关键点 :HTTP 只能传输字符串,JavaScript 对象必须用 JSON.stringify() 序列化后才能发送。
六、Demo:调用大模型 API 实战
这是最激动人心的部分------用 fetch 直接调用 DeepSeek 大模型,拿到 AI 的智能回复。
6.1 完整代码
javascript
// demo/script.js
// ① 请求行:POST + URL
const endpoint = 'https://api.deepseek.com/chat/completions';
// ② 请求头:告诉 API 我是谁、发什么格式
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer sk-xxxxxxxxxxxxxxxxxx`,
};
// ③ 请求体:我要问什么
const payload = {
model: 'deepseek-v4-flash', // 选择模型(flash = 更快更便宜)
messages: [
{
role: 'system',
content: 'You are a helpful assistant.',
},
{
role: 'user',
content: '你好, Deepseek',
},
],
};
try {
const response = await fetch(endpoint, {
method: 'POST', // 用 POST,因为要提交数据
headers,
body: JSON.stringify(payload), // 对象 → JSON 字符串
});
const data = await response.json();
// 拿到 AI 回复,渲染到页面
document.getElementById('reply').innerHTML = data.choices[0].message.content;
} catch (err) {
console.error(err);
}
6.2 流程拆解
sql
用户消息 "你好, Deepseek"
│
▼
┌──────────────────────────────┐
│ 构造 Messages: │
│ - system: 设定 AI 人设 │
│ - user: 用户实际输入 │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ fetch POST │
│ → api.deepseek.com │
│ → JSON.stringify(payload) │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 服务器处理(大模型推理) │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 返回 response │
│ data.choices[0] │
│ .message.content │
│ → AI 的回复文本 │
└──────────────────────────────┘
│
▼
渲染到页面 #reply 元素
6.3 关键细节解读
Messages 结构------和 AI 对话的核心约定:
| role | 含义 | 谁说的话 |
|---|---|---|
system |
系统提示词 | 你(开发者)给 AI 设定的行为准则 |
user |
用户消息 | 用户输入的问题 |
assistant |
AI 回复 | AI 的历史回答(多轮对话时使用) |
为什么选 deepseek-v4-flash? ------ 这是 DeepSeek 的轻量模型,速度快、价格低,适合日常使用。如果对质量要求更高,可以换成 deepseek-v4-pro 等更强大的版本。
try/catch 为什么必须写? ------ 网络请求充满了不确定性:服务器可能宕机、网络可能断开、API Key 可能过期......try/catch 确保即使出错了,程序也不会崩溃,而是优雅地处理异常。
七、前后端分离,到底分的是什么?
把 frontend + backend 的交互画成一张图:
javascript
┌──────────────────┐ HTTP ┌──────────────────┐
│ Frontend │ ────────────────────────> │ Backend │
│ │ │ │
│ index.html │ fetch("http://127.0.0.1 │ json-server │
│ main.js │ :3000/friends") │ data.json │
│ │ │ 端口 :3000 │
│ 负责: │ <──────────────────────── │ │
│ · 页面展示 │ 返回 JSON 数据 │ 负责: │
│ · 用户交互 │ │ · 数据存储 │
│ · 数据渲染 │ │ · 业务逻辑 │
└──────────────────┘ └──────────────────┘
这就是前后端分离的核心思想:
- 前端只管展示和交互,不碰数据库,不写业务逻辑
- 后端只管数据和逻辑,不画页面,不处理 DOM
- 两者通过 HTTP 协议 通信,各司其职,互不干扰
延伸:两种架构模式
B/S 架构(Browser / Server):浏览器 ↔ 服务器。你打开网页,浏览器向服务器发请求,服务器返回 HTML/CSS/JS。这是我们前端最常打交道的模式。
C/S 架构(Client / Server):客户端不限于浏览器------可以是 iOS App、Android App、桌面软件、小程序。它们都通过 HTTP 请求调用同一套后端 API。
markdown
┌──────────┐
│ 浏览器 │──┐
└──────────┘ │
┌──────────┐ │ HTTP/HTTPS ┌──────────────┐
│ iOS App │──┼──────────────────>│ 后端服务 │
└──────────┘ │ │ (统一 API) │
┌──────────┐ │ └──────────────┘
│ Android │──┘
└──────────┘
八、async/await:让异步代码像同步一样好读
JavaScript 是单线程的,但网络请求需要时间。如果同步等待,页面会卡死。所以我们需要异步编程。
async/await 是目前最优美的异步方案:
javascript
// 看这段代码,就读它的"执行顺序"
async function init() {
console.log('init start'); // 1️⃣ 先执行
const friends = await loadData(); // 2️⃣ 等数据(不阻塞 UI)
console.log(friends); // 3️⃣ 数据到了才往后走
renderData(friends); // 4️⃣ 渲染
}
init();
// init end 永远不会在 friends 之前打印
await 做了什么? ------ 它把异步操作"暂停"在原地,等 Promise 完成后再继续往下走。但注意------它只暂停当前 async 函数的执行,不会阻塞主线程,用户依然可以滚动页面、点击按钮。
⚠️ 铁律 :
await只能在async函数内部使用。
九、前端发送 HTTP 请求的两种方式
9.1 XMLHttpRequest ------ 经典方案
XMLHttpRequest(XHR)是 Ajax 时代的基石,虽然现在很少直接写,但理解它有助于面试和读老项目代码:
javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3000/friends');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
缺点:回调嵌套、代码冗长、没有 Promise 原生支持。
9.2 fetch ------ 现代标准
javascript
const res = await fetch('http://127.0.0.1:3000/friends');
const data = await res.json();
两行搞定。基于 Promise,天然支持 async/await,语法清爽。
📌 面试高频 :fetch 对比 XHR ------ ① fetch 基于 Promise,可链式调用;② fetch 默认不携带 cookie(需要设置
credentials: 'include');③ fetch 遇到 404/500 不会 reject(需要检查response.ok)。
十、调用 LLM 接口的另一种姿势:OpenAI SDK
除了用 fetch 裸写请求,实际项目中还可以用 OpenAI SDK。因为 DeepSeek 兼容 OpenAI 的接口规范,所以可以直接用 OpenAI 的 SDK 来调用:
javascript
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: 'sk-xxxxxxxxxxxxxxxxxx',
});
const completion = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: '你好, Deepseek' },
],
});
console.log(completion.choices[0].message.content);
SDK vs 裸 fetch,怎么选?
| 维度 | fetch 裸写 | OpenAI SDK |
|---|---|---|
| 依赖 | 零依赖 | 需要安装 openai 包 |
| 控制力 | 完全掌控每个细节 | SDK 封装了认证、重试等 |
| 学习价值 | 深入理解 HTTP 协议 | 快速开发、类型安全 |
| 适用场景 | 任意 HTTP API | OpenAI 兼容接口 |
💡 建议:先用 fetch 裸写一遍,搞清楚 HTTP 请求的底层是怎么回事;实际项目中再用 SDK 提效。先懂原理,再求效率。
十一、知识地图:一张图收尾
scss
┌─────────────────────────────────────────────────────────────────┐
│ 前端 HTTP 请求知识体系 │
├──────────────┬──────────────────────────────────────────────────┤
│ 请求方式 │ fetch (现代标准) / XMLHttpRequest (经典方案) │
├──────────────┼──────────────────────────────────────────────────┤
│ 报文结构 │ 请求行 (方法+URL) + 请求头 (元信息) + 请求体 (数据) │
├──────────────┼──────────────────────────────────────────────────┤
│ 架构模式 │ B/S (浏览器-服务器) / C/S (客户端-服务器) / 前后端分离│
├──────────────┼──────────────────────────────────────────────────┤
│ 网络基础 │ IP 地址 → 端口号 → 域名 → DNS 解析 │
├──────────────┼──────────────────────────────────────────────────┤
│ 异步控制 │ async/await 让异步代码像同步一样好读 │
├──────────────┼──────────────────────────────────────────────────┤
│ 数据转换 │ JSON.stringify ↔ JSON.parse / map + join 渲染 │
├──────────────┼──────────────────────────────────────────────────┤
│ 实战场景 │ ① 本地数据获取 ② AI 大模型 API 调用 │
├──────────────┼──────────────────────────────────────────────────┤
│ 进阶方向 │ OpenAI SDK / 流式响应 (SSE) / WebSocket │
└──────────────┴──────────────────────────────────────────────────┘
十二、写在最后
我们从零搭建了一个后端服务,用前端 fetch 拿到数据并渲染成表格,最后还成功调用了一次大模型 API,拿到了 AI 的智能回复。
回顾一下这条链路:
scss
Backend (json-server, 提供数据)
↑
│ HTTP GET
│
Frontend (fetch + map渲染, 展示数据)
Demo (fetch POST, 调用大模型 API, 获取 AI 回复)
你学到了什么:
- ✅ 用 json-server 快速搭建后端 API
- ✅ fetch 的 GET 和 POST 两种请求方式
- ✅ HTTP 请求报文的三要素(请求行、请求头、请求体)
- ✅ IP、端口、域名、DNS 的基础概念
- ✅ 前后端分离的架构思想(B/S、C/S)
- ✅
async/await优雅控制异步流程 - ✅ JSON 数据→HTML 视图的 map + join 渲染模式
- ✅ 调用大模型 API 的完整流程(Messages、API Key、模型选择)
- ✅ OpenAI SDK 与裸 fetch 的优劣对比
下一步你可以:
- 去 MDN 翻阅
fetch的官方文档,了解更多配置选项(超时、取消请求、跨域等) - 亲手跑通 json-server,感受前后端交互的全过程
- 尝试调用不同的大模型 API(通义千问、智谱 GLM、Moonshot 等),对比响应差异
- 思考进阶问题:数据量大时如何优化渲染?如何实现流式响应(一个字一个字蹦出来)?
前端和后端的 HTTP 通信,就像打电话------前端拨号(发请求),后端接听(处理),然后后端把话传回来(返回响应)。理解了这层关系,Web 开发的大门就已经向你敞开了。
本文所有示例代码均可独立运行,建议边读边练。动手,永远是学编程最快的方式。🚀