折腾两天 HTTP 接口调用,终于把 fetch 和前后端分离从书本概念落地到实操了

前几天写表格页面时踩了个小别扭:我把好友数据直接写在前端 json 里,改个姓名年龄就得去前端文件改代码,当时我懵了,这不就是十几年前前后端揉在一块的老式写法?

寻思着能不能把数据单独放到服务里,前端通过网络请求拉取数据,顺着这个想法,我拆出了现在三段式的项目目录。

从截图能看出来,我把项目粗暴分成三块:backend放后端服务、frontend做原生前端页面、demo单独用来测试第三方大模型接口。当时拆分文件夹的初衷很简单:把服务、业务页面、测试代码拆开,避免所有代码堆在一个文件夹里越写越乱。

先用 json-server 搭简易后端,跑通第一个本地前后端联调

把目录点开之后结构就更清晰了,后端靠 json-server 快速起服务,前端原生写页面,完全零框架,纯原生 JS 吃透请求逻辑。

说实话,之前看教材里的「前后端分离」四个字总觉得太虚,实操完这个小 demo 才算落地。拿生活化的例子类比:后端是奶茶后厨,存着原材料(data.json 数据),前端是进店的顾客(浏览器),顾客不能直接冲进后厨拿原料,只能通过点餐窗口(HTTP 接口 endpoint)下单取成品,这个点餐地址就是接口地址http://localhost:3000/friends

先贴后端配置,backend文件夹里就两个核心文件:

json 复制代码
// package.json
{
  "scripts": {
    "dev": "json-server --watch data.json --port 3000"
  }
}
json 复制代码
// data.json 数据源,后端唯一维护数据
{
    "friends":[
        {
            "id": 1,
            "name": "张三", 
            "age": 18
        },
        {
            "id": 2,
            "name": "李四", 
            "age": 20
        }
    ]
}

运行pnpm dev就能在 3000 端口拉起后端服务,数据改完只需要改这个 json 文件,前端不用动任何代码,这就是分离最直观的好处。

前端frontend里 html 只负责页面骨架,所有数据从接口动态获取,main.js是请求 + 渲染逻辑,这里我踩了当天第一个大坑。

javascript 复制代码
let friends = [];

async function loadData() {
    // endpoint:接口终点,笔记里标注的API请求地址,类比快递收件地址
    const endpoint = 'http://localhost:3000/friends';
    const res = await fetch(endpoint)
    const data = await res.json()
    return data;
}

// 拿到数据拼接表格DOM
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();
    renderData(friends);
}

init();
// 坑就在这一行,坑了我半小时
console.log('init end');

踩坑记录最开始我默认代码从上到下顺序执行,以为init()走完接口请求,才会打印init end,结果控制台永远先输出init end,再打印接口返回的数据。翻了 MDN 文档才醒悟:await 只会阻塞当前 async 函数内部的代码,全局顶层代码不受异步等待约束

顺带捋懂了 IP 和域名:127.0.0.1:3000里 IP 用来在网络里定位服务器,域名(比如www.baidu.com)是为了方便人类记忆,DNS 解析会自动把域名翻译成 IP 地址,而我们写的 endpoint 就是精准到具体资源的访问地址。

换个场景:用 fetch 调用 DeepSeek 线上大模型接口(demo 文件夹)

本地前后端跑通之后,我好奇 fetch 既然是通用 HTTP 请求方案,能不能直接调线上第三方接口?于是单独开了 demo 文件夹,写了个极简 AI 聊天页面。

这里又踩了第二个致命错误:POST 请求的 body 不能直接传 JS 对象。最开始随手把 payload 对象丢进 body 里,接口疯狂报错,反复试错才想起 HTTP 传输协议只认字符串 / 二进制流,浏览器没法解析原生 JS 对象,必须用JSON.stringify()序列化。

错误写法(千万别照搬):

javascript 复制代码
// 错误示范,直接传入对象,接口参数解析失败
const response = await fetch(endpoint, {
    method: 'POST',
    headers,
    body: payload 
})

修正后的可用代码:

javascript 复制代码
// demo/main.js
const endpoint = 'https://api.deepseek.com/chat/completions';
const headers = {
    'Content-Type': 'application/json',
    // 鉴权信息放在请求头,笔记里提到的请求头配置,最好写到.env文件中
    Authorization: `Bearer sk-xxx`
}

// 请求体:POST的数据载体
const payload = {
    model: 'deepseek-v4-flash',
    messages: [
        { role: 'system', content: 'You are a helpful assistant' },
        { role: 'user', content: '你好, Deepseek'}
    ]
}

try {
    const response = await fetch(endpoint, {
        method: 'POST',
        headers,
        // 关键:必须序列化转字符串
        body: JSON.stringify(payload)
    })
    const data = await response.json();
    document.getElementById('replay').innerHTML = 
        data.choices[0].message.content;
} catch(err) {
    // 捕获接口异常,避免页面白屏
}

搭配同目录的index.htmlstyle.css,一个简陋的 AI 对话页面就做完了。写完这个 demo,笔记里的「请求行、请求头、请求体」概念瞬间落地:

  • 请求行:接口地址 + 请求方法 + HTTP 版本,就是POST https://api.deepseek.com/xxx HTTP/1.1
  • 请求头:Content-Type、Authorization 这类配置信息
  • 请求体:POST 独有的数据载体,GET 请求没有 body

实操落地后,分清 B/S 和 C/S 架构

之前看书里的两种架构一直云里雾里,做完两个 demo 彻底通透:

  • B/S(浏览器 / 服务端) :咱们写的网页项目就是典型 BS,用户不用装任何软件,打开浏览器就能访问,前后端分离是 BS 架构主流开发模式;
  • C/S(客户端 / 服务端) :微信、抖音 APP 这类,需要下载安装客户端软件,再和后端服务通信。

另外前端发送 HTTP 不止 fetch 一种,笔记里还标注了XMLHttpRequest,是 fetch 出现之前的原生请求方案,老旧项目还能见到,新项目优先用 fetch 搭配 async/await。

梳理下来三个实打实的收获,顺便聊聊 fetch 局限性

折腾完两套 demo,抛开书本空话,实打实总结三点感悟:

  1. 前后端分离核心:页面渲染逻辑和数据源彻底拆分,后端只产出标准化 JSON 接口,前端只负责页面展示,修改数据源不用改动前端代码,这也是我拆分 backend/frontend 目录的初衷;
  2. endpoint 不是花哨命名:每个接口地址是服务资源的唯一入口,项目体量变大后统一管理接口地址,后续改域名、端口不用全项目搜索替换;
  3. async/await 是异步语法糖,极大优化了 fetch 的代码可读性,但一定要牢记 await 的作用域限制,这是新手写异步最容易踩的坑。

顺带一提,fetch 也不是万能方案:如果项目需要兼容 IE 等老旧浏览器,原生 fetch 无法使用,得降级用 XHR 或者 axios 二次封装。

如果你刚入门前后端接口调用,也踩过异步顺序、POST 序列化这类离谱 bug,搞懂了记得回来留个言,我也想看看你踩过哪些稀奇古怪的坑。

相关推荐
Ajie'Blog1 小时前
Claude Opus 4.8 发布:Claude Code 能不能接住复杂项目
服务器·前端·javascript·人工智能·ai编程
2501_918126911 小时前
火柴人踢任意球
javascript·css·css3
晓得迷路了2 小时前
栗子前端技术周刊第 132 期 - date-fns 支持 Temporal、npm 攻击事件、VoidZero...
前端·javascript·css
ct9782 小时前
Promise
前端·javascript·vue.js
怕浪猫2 小时前
Electron 开发实战(十一):自动更新机制|服务架构、公私网更新、版本回滚全解
前端·javascript·electron
AI视觉网奇2 小时前
three-bvh-csg glb分割
开发语言·前端·javascript
很楠爱上2 小时前
TypeScript 核心知识精要
javascript·ubuntu·typescript
zhangfeng11332 小时前
workbuddy ,node.js 每次会在 项目目录上安装 node_modules,能不能一次安装多次使用,为什么 npm 不把包装在全局
前端·npm·node.js
之歆2 小时前
Day06_Node.js 核心技术深度解析
node.js·编辑器·vim