米米商聊|前端学习笔记
用户先输入“项目”,接着输入“项目资料”。第二次请求先完成,页面显示了新结果;第一次请求随后返回,又把列表覆盖成旧结果。这里出错的不是排序,而是把“最后返回”误当成“当前需要”。下面用自造示例讨论这个问题,不涉及米米商聊客户端内部实现。
给一次搜索意图编号
每次搜索递增序号,只允许最新序号提交结果。检查应覆盖成功和失败:旧请求的错误提示,同样不应覆盖新请求的成功状态。清空输入也要递增,否则清空后的页面仍可能被旧结果重新填满。
load 接收字符串并返回最终结果的 Promise,publish 同步更新展示状态。为一个搜索区域保留一个实例,不要每次输入都重新创建。
export function createLatestSearch(load, publish) {
let sequence = 0;
let disposed = false;
return {
async run(query) {
if (disposed) return;
const id = ++sequence;
const isLatest = () => !disposed && id === sequence;
const term = query.trim();
if (!term) {
publish({
status: 'idle', items: [] });
return;
}
publish({
status: 'loading' });
let items;
try {
items = await load(term);
} catch (error) {
if (isLatest()) publish({
status: 'error',
message: error instanceof Error ? error.message : '请求失败'
});
return;
}
if (isLatest()) publish({
status: 'success', items });
},
dispose() {
disposed = true;
++sequence;
}
};
}
这个实例的 run 接收字符串。连续两次输入相同关键词,也属于两次调用,不能仅比较关键词是否相等。离开搜索区域时调用 dispose,后续完成的任务就不会再更新展示。
把检查放在最终数据之后
如果 load 内使用 fetch,应检查 response.ok,并完成正文解析后再返回数据。HTTP 错误状态不一定使 fetch 的 Promise 拒绝,response.json() 本身也异步执行;只在收到响应头时判断序号还不够。MDN:使用 Fetch
代码没有让每个请求在 finally 中统一设置“加载结束”。否则旧请求结束时,可能把仍在进行的新请求的 loading 清掉。本例用最新任务的 success 或 error 表达结束,过期任务不提交终态。Promise 的完成与失败都需要明确处理。MDN:Promise
序号与取消请求各管什么?
序号决定结果是否还有资格进入当前界面,不会停止网络传输。AbortController 可用于中止 fetch、响应体读取等操作,可以按需要配合使用,但不能把本例描述成已经实现请求取消或节省了多少带宽。MDN:abort
这也不是通用的请求处理规则。分页追加、需要全部保留的批量任务,应按自己的数据合并约定处理,不能照搬“只接受最新一次”。
本地验证覆盖什么?
本例已用可手动完成的 Promise 跑了九组 Node.js 断言:新请求先返回、旧请求先结束、旧请求失败、最新请求失败、清空输入、销毁实例、同步抛错、同关键词重复请求及空结果。全部通过,没有用随机延时碰运气。
测试只验证异步状态提交逻辑,没有调用真实搜索接口,也没有运行浏览器。接入页面后仍需检查输入事件、组件生命周期与真实错误提示。
创作说明:本文由 AI 辅助整理官方资料并起草;示例及本地断言由 AI 编写和执行。文中数据均为演示用途,不是产品性能或真机测试结论。