H5 Fetch 请求封装与竞态处理
H5 Fetch 请求封装与竞态处理
摘要:浏览器的 Fetch API 提供 Promise 式网络请求,但业务代码仍需处理状态码、解析失败、超时、取消和过期响应。本文构建一个轻量请求封装,并说明搜索和页面切换中的竞态处理方法。
一、理解 Fetch 的错误语义
Fetch 在网络中断、请求被取消等情况下会拒绝 Promise;收到 404 或 500 时通常仍会正常返回 Response。因此不能只靠 catch 判断服务端是否成功,还要检查 response.ok。
async function readJson(path, options = {}) {
const response = await fetch(path, {
...options,
headers: {
Accept: "application/json",
...options.headers,
},
});
if (!response.ok) {
throw new HttpError(response.status, response.statusText);
}
return response.json();
}
调用方需要决定返回体为空、内容不是 JSON 或服务端字段错误时如何处理。请求层不要假定所有响应都一定含有同一种数据。
二、用 AbortController 取消请求
AbortController 可以让页面在离开或查询条件变化时取消不再需要的请求:
const controller = new AbortController();
try {
const data = await readJson(requestPath, { signal: controller.signal });
render(data);
} catch (error) {
if (error.name !== "AbortError") showError(error);
}
function dispose() {
controller.abort();
}
取消后无需展示“网络失败”提示。组件卸载、路由切换或用户发出新查询时,都应取消不再使用的请求。
三、为请求设置超时
可将超时控制器与调用方的取消信号合并。环境支持 AbortSignal.timeout 和 AbortSignal.any 时可直接组合;需兼容较老环境时,则用计时器触发 AbortController,并在完成后清除计时器。
超时属于一种取消原因,错误界面可以与用户主动离开区分。超时时间应按请求用途设置;大文件上传和轻量列表读取不应强制使用相同阈值。
四、保护搜索结果顺序
快速输入可能产生多个并行请求。即使取消旧请求,某些计算或缓存层未必及时停止;可同时使用递增序号,只接受最新请求结果:
let requestVersion = 0;
let activeController;
async function search(query) {
const version = ++requestVersion;
activeController?.abort();
activeController = new AbortController();
try {
const result = await readJson(buildSearchPath(query), {
signal: activeController.signal,
});
if (version === requestVersion) renderResults(result);
} catch (error) {
if (error.name !== "AbortError" && version === requestVersion) {
showSearchError(error);
}
}
}
序号解决“较旧结果最后返回并覆盖新结果”的问题;取消则减少不必要工作。两者解决的问题不同,必要时可以一起使用。
五、统一处理请求体与错误
提交 JSON 时显式设置内容类型并序列化对象。服务端错误体可能是 JSON、文本或空内容,因此错误封装可以安全尝试解析,再提供状态码和业务错误码。不要直接把内部异常堆栈展示给用户。
鉴权令牌应从受控的认证模块注入;记录日志前清除敏感请求头和个人数据。不要在全局封装中无条件重试所有 POST,因为重复提交可能产生副作用。
六、组件中的状态管理
请求页面可用明确的 idle、loading、success 和 error 状态,避免多个布尔值互相矛盾。发起请求时进入 loading;成功后检查结果仍对应当前输入;失败时提供可恢复动作;卸载时清理监听和请求。
加载状态应与数据策略一致。刷新时保留旧内容可以减少闪烁,而首次进入页面可展示占位状态。请求封装负责传输,不应替页面决定空态和交互文案。
七、并发与缓存边界
同一资源的并发读取可以合并请求或由缓存层复用结果,但缓存键必须包含影响响应的参数和用户身份。退出账号后要清理用户隔离数据。缓存失效与重试策略属于数据层职责,需明确时效和一致性要求。
八、常见问题
- 不把 HTTP 错误状态误当成 Fetch 拒绝。
- 不把用户取消渲染为服务端故障。
- 对有副作用的请求谨慎自动重试。
- 页面销毁后停止更新 DOM,避免残留异步任务。
- 解析和校验数据结构,不能把
response.json()成功等同于数据有效。
九、总结
可靠的 Fetch 使用需要状态码检查、取消清理、超时语义和响应校验。搜索等高频场景还需要处理竞态,确保只有当前请求能更新界面。将传输错误与页面状态分开建模,封装才既可复用又不会隐藏业务决策。
- 点赞
- 收藏
- 关注作者
评论(0)