前端必会:从 Fetch 到 DeepSeek,一篇搞懂 HTTP 请求的方方面面

前端必会:从 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。简单的方法:

  1. 安装 json-server:npm install -g json-server
  2. 创建 db.json{ "friends": [{"id":1,"name":"张三","age":20}, ...] }
  3. 运行 json-server --watch db.json --port 3000
  4. 然后用浏览器打开 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 对象。
  • fetchbody 选项要求传入 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 如何控制执行顺序

现在,自己动手试试看吧!


相关推荐
半个烧饼不加肉1 小时前
JS 底层探究--执行上下文
开发语言·前端·javascript
小谢小哥1 小时前
68-持续集成详解
java·后端·架构
山河木马2 小时前
无框架-原生webGL渲染-底层入门-1
前端·javascript·webgl
A-刘晨阳2 小时前
数据库挂了服务就瘫?我用PostgreSQL主从流复制搭了高可用架构,cpolar打通远程访问
数据库·postgresql·架构
小李云雾2 小时前
深入浅出 Vue 3 核心知识点:从基础到实战
前端·javascript·vue.js·程序人生
candyTong2 小时前
为什么 Agent Skill 不是通过向量 RAG 召回的?
架构
Cobyte2 小时前
16.响应式系统比对:链表如何实现 computed 的高效更新
前端·javascript·vue.js
踩着两条虫2 小时前
开源 AI 低代码平台 VTJ.PRO 双版本齐发:核心引擎 v0.17.1 与在线平台 v2.4.1 正式上线,强化团队协作与 AI 资产管理
前端·人工智能·低代码·架构·开源
坏柠2 小时前
从一个设备控制面板开始,系统学习 LVGL 界面开发
android·javascript·学习