学习前提
先完成Fetch与Promise;理解防抖和组件加载状态。
本课完成后你能做什么
用请求编号保护界面,正确区分加载、成功、空结果、失败与取消。
先理解机制
用户先搜HTML再搜CSS,第二个请求可能先完成。若每次响应都直接写DOM,旧HTML结果会覆盖新CSS结果。这个问题与防抖不同:防抖减少发起次数,但已发出的工作仍可能乱序结束。先设计状态归属再增加优化。
每次发起分配递增编号,回调只在编号仍为最新时更新。成功、catch和finally都要遵守同一判断,否则旧请求虽然没有覆盖数据,却可能提前清除新请求的加载提示。只检查成功分支是不完整的修复。
AbortController可以取消支持signal的请求以节省资源,但不能保证所有异步工作都可取消,也不代表服务器回滚。编号检查保证界面一致性,取消负责资源管理。AbortError通常不应像服务器故障一样提示用户。
本例使用确定延迟的模拟服务来稳定复现乱序,不依赖外部网络。真实fetch还需检查HTTP状态、JSON解析和数据形状。测试时故意让旧请求更慢,比在正常网络下连续点击几次更容易证明正确性。
可运行示例
运行方式:将HTML示例保存为独立的.html文件并在浏览器打开;含脚本的示例使用现代浏览器。资源文件与多文件模块另按正文要求准备。
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Honeysuck practice</title></head><body>
<button id="old">Slow HTML request</button><button id="new">Fast CSS request</button><button id="fail">Failing request</button><p id="status" role="status">Ready</p><pre id="result"></pre>
<script>
let latest=0;
const status=document.querySelector('#status'),result=document.querySelector('#result');
function service(label,delay,fail=false){return new Promise((resolve,reject)=>setTimeout(()=>fail?reject(new Error('Demo failure')):resolve([label]),delay));}
async function search(label,delay,fail=false){const id=++latest;status.textContent='Loading '+label;result.textContent='';
try{const items=await service(label,delay,fail);if(id!==latest)return;result.textContent=items.join(', ');status.textContent=items.length?'Loaded':'No matches';}
catch(error){if(id!==latest)return;status.textContent='Could not load. Try again.';}
finally{if(id===latest)document.body.dataset.busy='false';}
}
for(const [selector,label,delay,fail] of [['#old','HTML',700,false],['#new','CSS',100,false],['#fail','Failure',150,true]])document.querySelector(selector).addEventListener('click',()=>{document.body.dataset.busy='true';search(label,delay,fail);});
</script>
</body></html>
推演与验证
先点Slow再立刻点Fast,最终只能显示CSS,即使等待超过700毫秒也不变。先点Fail再点Fast,旧失败不能覆盖新成功。单独点Fail应显示错误,随后点Fast能够恢复。将编号判断临时移除,确认测试能复现旧结果覆盖,再还原。
常见错误与边界
本例busy是演示状态,不会自动给所有控件加无障碍语义。实际界面可对结果区域设置aria-busy并提供状态文字。不要在finally无条件显示成功。
动手练习
增加空结果请求,并将结果区域aria-busy与当前请求同步。旧请求结束时不得修改它。
参考解法与理由
开始时设置busy为true;当前请求结束才设false;空数组进入明确空状态。用旧慢请求加新空请求测试,最终应保留空状态而不是旧数据。