用“用户数据看板”实战项目串联异步核心场景:串行依赖(用户→地区)、并行加载(用户+订单)、错误兜底、loading状态、防重复提交;纯JS实现,结构清晰、体验可控。

直接上手一个真实感强、覆盖主流场景的实战项目,比空讲概念快得多。核心思路是:用一个“用户数据看板”作为载体,把最常遇到的异步问题都串起来——串行依赖、并行加载、错误兜底、loading 状态、防重复提交。不堆砌功能,只做关键路径。
一、明确需求与接口约定
先定义好后端能提供的几个简单接口(可用 Mock Service Worker 或 JSON Server 快速模拟):
-
GET /api/user/:id → 返回用户基本信息(含
regionId) - GET /api/region/:id → 返回该地区名称(如“华东”)
- GET /api/orders?userId=:id → 返回该用户的最近 3 笔订单
- POST /api/feedback → 提交用户反馈(带防抖+loading)
目标页面:展示用户头像、昵称、所在大区、订单列表;底部有反馈表单。所有数据异步加载,状态可控、错误可感知。
二、分模块实现,每个模块解决一类问题
1. 串行获取用户 + 地区(依赖链)
必须先拿到 user.regionId,才能请求地区名。用 await 自然表达先后关系:
async function loadUserProfile(userId) {
try {
const user = await fetch(`/api/user/${userId}`).then(r => r.json());
const region = await fetch(`/api/region/${user.regionId}`).then(r => r.json());
return { ...user, regionName: region.name };
} catch (err) {
console.error('用户或地区加载失败', err);
throw new Error('用户信息不可用');
}
}
2. 并行加载订单 + 用户详情(无依赖)
用户信息和订单互不依赖,用 Promise.all 同时发起:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
async function loadDashboardData(userId) {
try {
const [userProfile, orders] = await Promise.all([
loadUserProfile(userId),
fetch(`/api/orders?userId=${userId}`).then(r => r.json())
]);
return { userProfile, orders };
} catch (err) {
// 注意:这里捕获的是任一 Promise 失败
throw err;
}
}
3. 表单提交 + 防重复 + loading 状态
用户点击提交时,禁用按钮、显示 loading,成功后清空输入框:
async function submitFeedback(formEl) {
const btn = formEl.querySelector('button');
const input = formEl.querySelector('input');
btn.disabled = true;
btn.textContent = '提交中...';
try {
await fetch('/api/feedback', {
method: 'POST',
body: JSON.stringify({ text: input.value }),
headers: { 'Content-Type': 'application/json' }
});
input.value = '';
alert('已提交');
} catch (err) {
alert('提交失败,请重试');
} finally {
btn.disabled = false;
btn.textContent = '提交';
}
}
三、组装页面逻辑(真实调用链)
在页面初始化时调用主函数,并统一处理 loading 和错误 UI:
async function initDashboard() {
const container = document.getElementById('dashboard');
container.innerHTML = '<div class="loading">加载中...</div>';
try {
const data = await loadDashboardData(123);
renderDashboard(data); // 自定义渲染函数
} catch (err) {
container.innerHTML = `<div class="error">加载失败:${err.message}</div>`;
}
}
// 绑定事件
document.getElementById('feedback-form').addEventListener('submit', e => {
e.preventDefault();
submitFeedback(e.target);
});
initDashboard();
四、进阶点睛(加一点就提效)
不是必须,但加上会让项目更贴近生产环境:
- 用
Promise.allSettled替代Promise.all:订单加载失败不影响用户信息展示 - 给 fetch 封装统一超时和错误分类(如网络错误 vs 业务错误)
- 在
loadUserProfile中加缓存逻辑:if (cache[userId]) return cache[userId] - 用 AbortController 实现请求取消(比如用户快速切换 tab)
整个过程没用任何框架,纯 JS + 原生 fetch,但结构清晰、职责分明、错误不漏、体验可控。做完这个,90% 的接口类页面逻辑你都能自己拆解落地。

















