下载工作台
前端开发入门

异步编程与 Fetch API

试读上半部分 · 解锁后可读全文

第 12 章 · 异步编程与 Fetch API

本章目标:理解 JavaScript 单线程与事件循环,掌握 Promise、async/awaitFetch API,完成 academy-demo 与课程 REST 接口的联调,并学会用 mock 数据独立开发。

前置:第 8、11 章;了解 HTTP 状态码基础。


12.1 同步 vs 异步

浏览器主线程负责解析 HTML、执行 JS、渲染页面。网络请求、定时器、文件读取等耗时操作不能阻塞界面,因此采用异步回调。

调用 fetch()
    ↓
立即返回 Promise(pending)
    ↓
主线程继续执行后续代码
    ↓
网络响应到达 → 微任务队列
    ↓
Promise 变为 fulfilled / rejected → 执行 then / await 后续
模型特点问题
回调cb(err, data)回调地狱
Promise链式 .then错误需 .catch
async/await同步写法风格仍需 try/catch

12.2 Promise 基础

const p = new Promise((resolve, reject) => {
  setTimeout(() => resolve({ id: 'fe-12', title: '异步编程' }), 500);
});

p.then(data => console.log(data))
 .catch(err => console.error(err))
 .finally(() => console.log('done'));

// 静态方法
Promise.all([fetch('/api/a'), fetch('/api/b')]);
Promise.allSettled([p1, p2]); // 不因单个失败而中断
Promise.race([fetch(url), timeout(5000)]);
状态含义可转换
pending进行中→ fulfilled / rejected
fulfilled成功终态
rejected失败终态

12.3 async / await

async function loadCourses() {
  try {
    const res = await fetch('/api/academy/courses');
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`);
    }
    const { data } = await res.json();
    return data;
  } catch (err) {
    console.error('加载课程失败', err);
    showToast('网络异常,请稍后重试');
    return [];
  }
}

// 顶层 await(module 内)
const courses = await loadCourses();
规则说明
async 函数一定返回 Promise返回值被包装
await 暂停当前 async 函数不阻塞整个页面
错误用 try/catch.catch
并行请求await Promise.all([...])
// 串行(慢)
const a = await fetchA();
const b = await fetchB(a.id);

// 并行(快)
const [courses, tags] = await Promise.all([
  fetch('/api/courses').then(r => r.json()),
  fetch('/api/tags').then(r => r.json()),
]);

12.4 Fetch API 详解

const res = await fetch('https://api.example.com/academy/courses', {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer <token>', // 需服务端支持
  },
  credentials: 'include', // 携带 Cookie(跨域需 CORS 配置)
});

// POST JSON
await fetch('/api/academy/enroll', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ courseId: 'fe-12', userId: 'u001' }),
});
选项作用
methodGET / POST / PUT / PATCH / DELETE
headers内容类型、鉴权
bodyPOST 请求体(字符串 / FormData)
signalAbortController 取消请求
credentialsCookie 跨域策略

12.4.1 请求取消

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 8000);

try {
  const res = await fetch('/api/courses', { signal: controller.signal });
  clearTimeout(timer);
  // ...
} catch (e) {
  if (e.name === 'AbortError') console.warn('请求超时已取消');
}

12.5 REST 联调约定

academy-demo 假设后端提供如下接口(贤紫学院 BFF 风格):

方法路径说明响应示例
GET/api/academy/courses课程列表{ "data": [...], "total": 16 }
GET/api/academy/courses/:id课程详情{ "data": { "id", "title", "chapters" } }
GET/api/academy/todos用户待办{ "data": [{ "id", "text", "done" }] }
POST/api/academy/todos新建待办{ "data": { ... } }
PATCH/api/academy/todos/:id更新状态{ "data": { ... } }
DELETE/api/academy/todos/:id删除204 No Content

12.5.1 封装 API 客户端

// academy-demo/js/api/client.js
const BASE = import.meta.env?.VITE_API_BASE ?? '/api/academy';

async function request(path, options = {}) {

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。