深入 JavaScript:搜索请求返回乱序怎样处理

用户先搜甲再搜乙,乙先返回、甲后返回时,旧结果可能覆盖当前输入。

edyang · 2016-04-25

用户先搜甲再搜乙,乙先返回、甲后返回时,旧结果可能覆盖当前输入。

本文使用ECMAScript 5.1 与浏览器 DOM,代码可放入独立演示页。JavaScript(脚本语言)的作用域、函数调用与事件调度,需要与浏览器提供的接口区分。本文主体使用当时已经可用的语法,不把语言版本和宿主环境混为一谈。

1. 从现象推到机制

异步请求完成顺序不受发出顺序保证。取消旧请求可以减少浪费,但不能单独依赖取消成功。为每次查询分配递增序号,渲染时只接受最新序号对应的结果,就能明确结果归属。

深入 JavaScript:搜索请求返回乱序怎样处理的处理、反馈与观察关系

图中的四个节点分别表示输入变化、异步请求、结果归属、页面更新。顺着实线检查输入如何变成输出,再沿虚线核对反馈条件。图示表达的是本文讨论的关系,不是实测曲线或现场截图。

2. 最小实现与输入条件

用 XMLHttpRequest 的演示封装发请求,保存当前序号。服务端故意设置不同延迟,在回调中比较序号与当前输入状态。

1
2
3
4
5
6
7
8
var sequence = 0;
function search(query) {
  var mine = ++sequence;
  request(query, function(result) {
    if (mine !== sequence) return;
    render(result);
  });
}

先记录输入、调用位置与控制台结果,再逐个改变条件。事件和异步实验保留触发顺序,避免在生产页面执行带有未知副作用的调试代码。

3. 用对照区分原因

下表列出需要主动改变的条件。预期行为来自机制分析,复现时还需记录运行环境和原始输出,不能将预期当作已经测得的结果。

改变的条件 需要验证的行为
旧请求晚于新请求返回 旧结果不得覆盖当前列表
用户清空关键词 也应更新当前查询身份
尝试取消旧请求 仍保留归属校验更稳妥

对照 1:旧请求晚于新请求返回

旧结果不得覆盖当前列表。观察时记录请求序号与当前序号。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

对照 2:用户清空关键词

也应更新当前查询身份。观察时记录清空后的页面状态。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

对照 3:尝试取消旧请求

仍保留归属校验更稳妥。观察时记录取消与回调之间的竞争。把这个条件与基准情形分开,有助于判断变化来自哪一层;若得到相反结果,应先核对前提,再修改实现。

4. 适用条件与取舍

防抖减少请求频率,序号控制结果归属,两者解决的问题不同。分页或多区域请求还应有各自独立的查询身份。

一次正常运行只覆盖了这组输入。继续扩展前,应把上面的异常对照保留下来,尤其关注“尝试取消旧请求”这一分支。后续方案若改变了图中任意一处依赖,原来的判断就要重新验证,不能仅凭界面相似或返回值相同沿用结论。

同月延伸

参考资料

  1. ECMAScript 5.1 规范
  2. HTML5 浏览器处理模型