目标:统一异步编程规范,提升可维护性与稳定性。
📘 异步编程总体原则
一、设计理念
- 可控性优先:异步逻辑必须可追踪、可中断、可恢复。
- 一致性优先:全项目统一使用
async/await风格,禁止混用回调与 Promise 链。 - 异常外抛:错误必须向上传递,不得静默吞掉。
- 性能优先:异步并行应显式控制,避免隐式顺序阻塞。
- 可调试性:确保堆栈、日志、监控能清晰还原异步执行路径。
二、统一代码规范
| 规则 | 说明 | 示例 |
|---|---|---|
✅ 使用 async/await | 默认风格 | const data = await fetchData() |
❌ 禁止 .then().then() 链式写法 | 可读性差 | fetch().then().then() ❌ |
| ✅ 错误捕获 | 使用 try/catch 或统一包装器 | await safely(fn) |
| ✅ 并行执行 | Promise.all 或 allSettled | await Promise.all([a(), b()]) |
| ✅ 限制并发 | 使用 limiter 工具 | await runLimited(tasks, 5) |
| ✅ 命名规范 | 异步函数以 Async 结尾 | getUserAsync() |
| ✅ 明确类型 | 返回 Promise<T> | TypeScript 规范 |
| ❌ 禁用同步 API | 避免主线程阻塞 | readFileSync() 禁用 |
三、事件循环与微任务机制
graph TD
A[任务提交] --> B[宏任务队列]
B --> C[执行栈空闲]
C --> D[微任务队列]
D --> E[渲染更新或 I/O 回调]
执行顺序:
1️⃣ 执行栈清空 →
2️⃣ 执行所有微任务(Promise 回调等)→
3️⃣ 进入下一轮宏任务(如 setTimeout)。
💡 微任务在同一轮事件循环中立即执行,因此:
- 使用
await时,Promise 回调将优先于下一个setTimeout执行。- 大量微任务可能导致渲染延迟。
⚙️ 常见反模式与改进
| 反模式 | 问题 | 改进建议 |
|---|---|---|
| 嵌套回调 (Callback Hell) | 可读性差、难调试 | 用 async/await 重构 |
| Promise 链过长 | 依赖复杂,错误难定位 | 拆分子函数 |
| 未捕获错误 | Promise Rejection 无监控 | 全局监听 + try/catch |
滥用 Promise.all | 任一失败导致全局中断 | 改用 allSettled |
| 循环中 await | 串行阻塞性能差 | 用 Promise.all 并行 |
| 空的 catch 块 | 吞掉错误 | 用 handleError() 统一处理 |
| 依赖执行时序 | 逻辑不稳定 | 显式 await 或任务序列化 |
| 主线程阻塞 | 卡顿、掉帧 | 移至 Web Worker / 后台线程 |
❌ 反例
getUser(id)
.then(u => getPosts(u.id))
.then(p => getComments(p))
.catch(() => {}); // 错误被吞掉
✅ 推荐写法
try {
const user = await getUserAsync(id);
const posts = await getPostsAsync(user.id);
const comments = await getCommentsAsync(posts);
} catch (err) {
logger.error('Async flow failed:', err);
}
🧩 推荐模板
1️⃣ 异步函数基础模板
export async function fetchUserAsync(id: string): Promise<User> {
try {
const res = await api.get(`/users/${id}`);
return res.data;
} catch (error) {
handleAsyncError(error, 'fetchUserAsync');
throw error;
}
}
2️⃣ 并行 + 限流模板
import pLimit from 'p-limit';
export async function fetchAllUsersAsync(ids: string[]): Promise<User[]> {
const limit = pLimit(5);
const tasks = ids.map(id => limit(() => fetchUserAsync(id)));
return await Promise.allSettled(tasks);
}
3️⃣ 安全执行包装器
export async function safely(fn: () => Promise<any>) {
try {
return await fn();
} catch (err) {
logger.error(err);
return null;
}
}
4️⃣ 可取消任务模板
function cancellableFetch(url) {
const controller = new AbortController();
const fetchPromise = fetch(url, { signal: controller.signal });
return {
promise: fetchPromise,
cancel: () => controller.abort()
};
}
✅ 团队检查清单
🔍 编码检查
| 检查项 | 状态 | 说明 |
|---|---|---|
[ ] 所有异步函数以 Async 结尾 | 命名规范 | |
| [ ] 所有异步调用均捕获错误 | 无 unhandled rejection | |
[ ] 禁止 .then 链式调用 | 统一风格 | |
| [ ] 所有文件 IO 为异步版本 | fs/promises | |
| [ ] 所有并行操作显式控制 | 使用 Promise.all | |
| [ ] 无空 catch 块 | ||
| [ ] 日志记录统一 | 错误集中处理 | |
| [ ] 异步操作具备超时机制 | 使用 Promise.race | |
| [ ] 微任务行为与预期一致 | 浏览器 / Node | |
| [ ] Worker 中不访问 DOM | 保持线程安全 |
🧠 性能优化建议
- 并发数控制:建议 5~10 并发,防止资源争夺。
- 重试机制:使用
p-retry提升网络稳定性。 - 任务分组:大任务拆分为可控子任务,防止事件循环长时间阻塞。
- 空闲任务:使用
requestIdleCallback或 Web Worker。
📚 推荐工具
| 工具 | 用途 |
|---|---|
eslint-plugin-promise | 检查异步反模式 |
p-limit | 并发控制 |
p-retry | 自动重试机制 |
p-timeout | 超时封装 |
async-local-storage | 异步上下文追踪 |
💬 团队约定语录
“异步不可怕,可怕的是没有规则的异步。”
—— 团队异步标准委员会