从前端 HTTP 请求到 LLM 接口调用:一篇文章带你彻底搞懂

30 阅读11分钟

无论你是刚入门的前端新手,还是正在探索 AI 应用开发的工程师,理解 HTTP 请求都是绕不开的基本功。本文将通过一个完整的 Demo 项目,从最基础的本地数据请求讲起,一步步带你掌握从前端发送 HTTP 请求、理解前后端分离架构,到最终调用大模型 API 的完整链路。


一、项目概览:三个模块,一条链路

整个 Demo 项目由三个模块组成,恰好覆盖了前端 HTTP 请求的三大典型场景:

demo1/
├── backend/          ← 后端:用 json-server 快速搭建 REST API
├── frontend/         ← 前端:从后端获取数据并渲染到页面
└── demo/             ← AI实战:调用 DeepSeek 大模型 API
模块角色核心能力
backend数据提供方提供 http://127.0.0.1:3000/friends 接口
frontend数据消费方fetch 获取数据 → 渲染 HTML 表格
demoAI 接口调用fetch POST 调用大模型,拿到 AI 回复

下面我们逐个拆解,从最简单的开始。


二、Backend:三分钟搭建一个后端服务

很多人觉得"后端"很神秘,其实一个能用的后端,三分钟就能搭好。

2.1 数据准备

// data.json — 这就是我们的"数据库"
{
  "friends": [
    { "id": 1, "name": "张三", "age": 18 },
    { "id": 2, "name": "李四", "age": 20 }
  ]
}

一个 JSON 文件,两个好友对象,简单到不能再简单。

2.2 一行命令启动服务

// package.json
{
  "scripts": {
    "dev": "json-server --watch data.json --port 3000"
  }
}

json-server 是一个零配置的 REST API 工具,它做了什么?

  1. 读取 data.json 文件
  2. 自动生成 RESTful 接口——GET /friends 返回好友列表
  3. 监听 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,这个地址每个部分都有含义:

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.com110.242.68.66 好记一万倍,域名就是 IP 的"别名"
  • DNS:把域名翻译成 IP 地址的"翻译官"。你输入 www.baidu.com,DNS 帮你查出背后的 IP,然后浏览器才能找到服务器

四、Frontend:从数据获取到页面渲染

有了后端,前端怎么拿数据?来看 frontend 模块的完整代码。

4.1 页面骨架

<!-- 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 + 渲染

// 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

friends.map(function (friend) {
  return `<tr>...</tr>`;
}).join('');

这条链式调用干了什么?

  1. map:遍历数组中的每个 friend 对象,把它转换成一行 <tr> HTML 字符串。[{id:1,name:'张三'}, ...]['<tr>...张三...</tr>', '<tr>...李四...</tr>']
  2. join(''):把字符串数组拼接成一个大字符串。['<tr>...</tr>', '<tr>...</tr>']'<tr>...</tr><tr>...</tr>'
  3. innerHTML =:一次性写入 DOM,浏览器渲染出两行表格

📌 这其实就是 React / Vue 虚拟 DOM diff 之前最原始的渲染方式——数据变了,重新生成 HTML,整体替换。理解了这一点,就理解了为什么现代框架需要虚拟 DOM 来优化性能。


五、HTTP 请求的完整结构

上面 loadData() 里的 fetch(endpoint) 是一个最简单的 GET 请求。但一个完整的 HTTP 请求,其实由三部分组成:

┌──────────────────────────────────────────┐
│               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 完整代码

// 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 流程拆解

用户消息 "你好, 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用户消息用户输入的问题
assistantAI 回复AI 的历史回答(多轮对话时使用)

为什么选 deepseek-v4-flash —— 这是 DeepSeek 的轻量模型,速度快、价格低,适合日常使用。如果对质量要求更高,可以换成 deepseek-v4-pro 等更强大的版本。

try/catch 为什么必须写? —— 网络请求充满了不确定性:服务器可能宕机、网络可能断开、API Key 可能过期……try/catch 确保即使出错了,程序也不会崩溃,而是优雅地处理异常。


七、前后端分离,到底分的是什么?

把 frontend + backend 的交互画成一张图:

┌──────────────────┐           HTTP            ┌──────────────────┐
│     Frontend      │ ────────────────────────> │     Backend       │
│                   │                           │                   │
│  index.htmlfetch("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。

         ┌──────────┐
         │  浏览器   │──┐
         └──────────┘  │
         ┌──────────┐  │    HTTP/HTTPS     ┌──────────────┐
         │  iOS App │──┼──────────────────>│   后端服务    │
         └──────────┘  │                   │  (统一 API)   │
         ┌──────────┐  │                   └──────────────┘
         │ Android  │──┘
         └──────────┘

八、async/await:让异步代码像同步一样好读

JavaScript 是单线程的,但网络请求需要时间。如果同步等待,页面会卡死。所以我们需要异步编程

async/await 是目前最优美的异步方案:

// 看这段代码,就读它的"执行顺序"
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 时代的基石,虽然现在很少直接写,但理解它有助于面试和读老项目代码:

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 —— 现代标准

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 来调用:

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 APIOpenAI 兼容接口

💡 建议:先用 fetch 裸写一遍,搞清楚 HTTP 请求的底层是怎么回事;实际项目中再用 SDK 提效。先懂原理,再求效率。


十一、知识地图:一张图收尾

┌─────────────────────────────────────────────────────────────────┐
│                     前端 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 的智能回复。

回顾一下这条链路:

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 开发的大门就已经向你敞开了。


本文所有示例代码均可独立运行,建议边读边练。动手,永远是学编程最快的方式。🚀