在 async 函数里,Promise 的 pending 状态就像一个看不见的“黑箱”——你不能直接伸手进去探测它,也干预不了它。说到底,它只是个中间态,async/await 自己会搞定后续的“成功或失败”。所以,真正棘手的问题不在于怎么“看”到它,而在于当程序处在这个等待期时,我们该如何进行有效的“响应”与“控制”:比如,给用户一个加载反馈、防止请求卡死、支持中途取消,或是避免因为手滑点了两下而触发重复请求。

让加载状态“看得见”
前端开发中最常见的需求:用户点个按钮,你得告诉他“正在加载”。这个场景大家肯定不陌生。async 函数不给你暴露 pending 状态,但我们可以用状态变量和 await 巧妙地“打个配合”。
- 先定义一个响应式的状态变量(React 的 useState,Vue 的 ref,或就是一个普通布尔值)。
- 在 await 之前,干脆利落地把它设为 loading = true;等 await 执行完,再利落地设回 false。
- 注意一个坑:如果 Promise 永远处于 pending 状态(比如网络断了没反应),你的 loading 状态就会一直转圈,用户界面也就跟着一起“死锁”了。所以,超时机制是必须的。
给 Promise 上个“闹钟”:超时保护
原生 Promise 没有超时时间这个概念。一个请求挂起太久,界面就假死了,这是实战中最让人头疼的问题之一。解决办法也不复杂,用 Promise.race 给它套个“定时冲击波”:
function timeout(ms, promise) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), ms);
return Promise.race([
promise,
new Promise((_, reject) =>
controller.signal.addEventListener('abort', () =>
reject(new Error('Request timed out'))
)
)
]).finally(() => clearTimeout(timeoutId));
}
// 在 async 函数中使用
async function fetchData() {
try {
const data = await timeout(5000, fetch('/api/data').then(r => r.json()));
return data;
} catch (err) {
console.error(err.message); // 输出 "Request timed out" 或其他错误
}
}
“不想等了,我要取消”:支持可取消的异步操作
你发出的请求,万一用户想取消怎么办?挂起中的 Promise 是无法从外部“掐断”的。但好在我们有 AbortSignal 这个利器,它可以让底层 API(比如 fetch)响应你的取消指令。
- 创建一个 AbortController 实例,把它身上的 signal 传给 fetch 或你的自定义异步函数。
- 在 await 之前保存好这个 controller,当你想要取消时,直接调用 controller.abort() 即可。
- 被取消的 fetch 会立刻 reject,并抛出一个 DOMException("AbortError")。
来看个例子,更直观:
let controller;
async function startRequest() {
// 取消上一次未完成的请求,避免混乱
controller?.abort();
controller = new AbortController();
try {
const res = await fetch('/slow-api', { signal: controller.signal });
return await res.json();
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求已被取消');
} else {
throw err;
}
}
}
防患于未然:避免意外的 pending 积压
这一点特别容易在搜索框、按钮快点击这类场景中间出现。你快速触发了同一个 async 函数很多次,结果每次都会产生一个新的 pending Promise。这不仅浪费资源,还可能因为返回顺序不一致导致状态错乱,结果还没回来,又发下一次。
- 用防抖(debounce)来“节流”:只保留最后一次请求。
- 用 Promise.allSettled 或 Promise.all 来协调多个并行请求,确保它们都完成后再统一处理。
- 最干脆的做法:对已经发出但还没结束的请求,主动调用 abort() 取消它,而不是让它默默 pending 着。
说了这么多,其实核心思想很简单:async 函数中的 pending 是无形的,关键不在于如何“观测”它,而在于如何“设计”好一次异步操作的完整生命周期——从哪里开始,如何中断,以及怎样结尾。这才是控制异步流程的制胜点。
