前端异步请求踩坑?3种方式搞定Ajax数据交互,从XHR到async/await

作为前端开发者,你是不是也遇到过这些问题:

  • 想在页面不刷新的情况下获取后端数据,却不知道怎么下手?
  • 用XHR写Ajax请求,回调嵌套绕到晕?
  • 异步代码执行顺序混乱,console.log先出end再出数据?

这篇文章我会用实战案例,从基础的XHR到现代的async/await,手把手教你3种前端异步请求方式,彻底搞懂JS异步逻辑,搞定前后端数据交互!

一、先搞懂:为什么需要Ajax?

在Web2.0时代,我们要实现页面不刷新就能动态更新内容(比如待办列表、商品数据),就必须用Ajax发起HTTP请求。

核心逻辑:

  • JS是单线程语言,遇到异步任务不会阻塞,会丢到eventloop队列
  • 等异步任务执行时机到了,eventloop再把任务放回JS线程执行
  • Ajax就是最典型的异步场景,核心是「后台请求数据+前端动态渲染」

二、实战场景:搭建本地接口+前端请求渲染待办列表

先搭一个极简的本地Node服务,提供待办列表接口,再用不同方式请求数据渲染页面,全程代码可直接复制运行!

第一步:搭建本地Node服务(提供/todos接口)

新建index.js文件,复制以下代码:

javascript 复制代码
// 引入Node内置http模块
const http = require("http");

// 创建HTTP服务
http.createServer((req, res) => {
  // 模拟待办列表数据
  const todos = [
    { id: "1", title: "过四六级", completed: false },
    { id: "2", title: "回家过节", completed: false }
  ];

  // 🔥 踩坑提醒:跨域必加!否则前端请求会报错
  res.setHeader("Access-Control-Allow-Origin", "*");
  // 告诉前端响应数据是JSON格式
  res.setHeader("Content-Type", "application/json; charset=utf-8");

  // 接口路由处理
  if (req.url === "/") {
    res.end("hello world");
  }
  if (req.url === "/todos") {
    // 把对象序列化为JSON字符串(网络传输必须转字符串)
    res.end(JSON.stringify(todos));
  }
}).listen(3000, () => {
  console.log("server is running at 3000 port");
});

运行命令:

bash 复制代码
node index.js

此时访问http://localhost:3000/todos,就能看到JSON格式的待办数据,接口搭建完成!

第二步:前端页面准备(基础结构)

新建index.html文件,基础结构如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ajax实战</title>
</head>
<body>
  <!-- 待办列表容器 -->
  <ul class="todo-list"></ul>
  <script>
    // 后续请求代码写在这里
  </script>
</body>
</html>

三、3种Ajax请求方式,从旧到新逐个讲

方式1:传统XHR(XMLHttpRequest)------ 基础但繁琐

这是Ajax的「老前辈」,虽然写法繁琐,但理解它能搞懂异步本质!

把以下代码替换到index.html的script标签中:

javascript 复制代码
// 1. 实例化XHR对象
const xhr = new XMLHttpRequest();
// 2. 打开通信通道:GET请求 + 接口地址 + 是否异步(true=异步)
xhr.open("GET", "http://localhost:3000/todos", true);

console.log("start");

// 3. 监听状态变化(回调函数)
xhr.onreadystatechange = function() {
  // 🔥 踩坑提醒:必须判断状态码,否则会多次触发
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 把JSON字符串解析为JS对象
    const todos = JSON.parse(xhr.responseText);
    console.log(todos);
    // 渲染到页面
    document.querySelector(".todo-list").innerHTML = todos.map(todo => `
      <li>${todo.title}</li>
    `).join("");
  }
};

// 4. 发送请求
xhr.send();

console.log("end");

运行结果&踩坑点:

  • console先输出startend,再输出todos数据(异步特性)
  • 必踩坑:忘记判断readyState === 4status === 200,会导致代码多次执行
  • 缺点:回调嵌套多了会形成「回调地狱」,代码可读性差

方式2:Promise封装(fetch)------ 简化回调

fetch是基于Promise的新一代请求方式,写法更简洁,无需手动监听状态!

替换script标签代码:

javascript 复制代码
console.log("start");

// fetch返回Promise对象,用then处理异步结果
fetch("http://localhost:3000/todos")
  // 第一步:解析响应为JSON
  .then(res => res.json())
  // 第二步:渲染数据
  .then(todos => {
    console.log(todos);
    document.querySelector(".todo-list").innerHTML = todos.map(todo => `
      <li>${todo.title}</li>
    `).join("");
  })
  // 🔥 踩坑提醒:fetch仅捕获网络错误,HTTP错误(如404)需手动判断
  .catch(err => console.error("请求失败:", err));

console.log("end");

优点&踩坑点:

  • 优点:链式调用替代回调嵌套,代码更清晰
  • 踩坑点:fetch不会自动拦截404/500等HTTP错误,需在第一个then里判断res.ok

方式3:async/await ------ 异步代码「同步化」(推荐)

这是目前最优雅的写法,基于Promise封装,代码和同步逻辑几乎一致,可读性拉满!

替换script标签代码:

javascript 复制代码
// 封装为async函数
async function getTodos() {
  try {
    console.log("start");
    // 等待请求响应
    const res = await fetch("http://localhost:3000/todos");
    // 🔥 踩坑提醒:手动判断响应状态
    if (!res.ok) {
      throw new Error(`HTTP错误:${res.status}`);
    }
    // 等待解析JSON
    const todos = await res.json();
    // 渲染数据
    document.querySelector(".todo-list").innerHTML = todos.map(todo => `
      <li>${todo.title}</li>
    `).join("");
    console.log(todos);
    console.log("end");
  } catch (err) {
    console.error("请求失败:", err);
  }
}

// 执行函数
getTodos();

核心优势:

  • 代码线性执行,和同步代码逻辑一致,新手也能看懂
  • try/catch统一捕获错误,无需在then里单独处理
  • 是目前前端异步请求的「最优解」,大厂项目几乎都用它

四、关键知识点补充(必记)

1. JSON.stringify/JSON.parse

  • JSON.stringify(obj):把JS对象转为JSON字符串(网络传输必须用字符串)
  • JSON.parse(str):把JSON字符串转回JS对象
  • 可选参数:JSON.stringify(value, replace, space)
    • replace:筛选/修改序列化的属性(null=原样序列化)
    • space:设置空格数,提升可读性(团队规范常用2/4个空格)

2. 异步执行核心逻辑

  • JS单线程 → 异步任务入eventloop队列 → 主线程空闲后执行
  • await会「暂停」async函数执行,等待Promise完成后再继续
  • 异步代码执行顺序:同步代码先执行,异步代码后执行(比如前面的start/end输出顺序)

五、总结

今天我们用「待办列表」实战场景,讲了3种Ajax请求方式:

  1. XHR:基础底层,理解异步本质,但写法繁琐
  2. fetch+Promise:简化回调,链式调用更清晰
  3. async/await:异步代码同步化,可读性最高,推荐生产环境使用

核心收获:

  • 搞定前后端跨域(设置Access-Control-Allow-Origin)
  • 掌握异步代码执行逻辑,避免顺序混乱
  • 能独立搭建本地接口+前端请求渲染页面
相关推荐
悟空瞎说1 小时前
最新 React Native 推送通知完整实战指南
前端
李白的天不白1 小时前
pnpm 启动前端项目
前端
lvchaoq1 小时前
从原理层面解释前端大数据量性能优化系列——分片加载
前端
杨先生哦2 小时前
2026 热端攻防:AI 驱动 Web 前端安全全景透析
前端·笔记·安全·web安全
李白的天不白2 小时前
SmartAdmin(基于 Spring Boot 框架)中配置跨域请求 VUE3 设置请求头
java·前端
一个被程序员耽误的厨师2 小时前
01-设计篇-我用前端那一套手艺造了一个AI-Native工具
前端·ai-native
不吃糖葫芦32 小时前
vue3实现拓扑图编辑功能(谨以此纪念我当前的最后一份前端工作)
前端
大家的林语冰2 小时前
超越 TypeScript,Flow 强势回归,语法高仿 TS,功能更丰富,类型更安全!
前端·javascript·typescript
星空2 小时前
html\css\js入门
javascript·css·html