由于许多公共API(例如GitHub公共API)都有请求限制,因此我们有必要实施某种缓存机制以避免不必要的请求调用。但是我发现这可能会引起比赛状况。
我编写了一个示例来演示这种情况 https://codesandbox.io/s/race-condition-9kynm?file=/src/index.js
我首先在这里实现cachedFetch
const cachedFetch = (url, options) => {
// Use the URL as the cache key to sessionStorage
let cacheKey = url;
let cached = sessionStorage.getItem(cacheKey);
if (cached !== null) {
console.log("reading from cache....");
let response = new Response(new Blob([cached]));
return Promise.resolve(response);
}
return fetch(url, options).then(async response => {
if (response.status === 200) {
let ct = response.headers.get("Content-Type");
if (ct && (ct.includes("application/json") || ct.includes("text"))) {
response
.clone()
.text()
.then(content => {
sessionStorage.setItem(cacheKey, content);
});
}
}
return response;
});
};
它使用sessionStorage
来缓存结果。
我正在向Github API发送请求。这个想法很简单,有一个Input
和一个p
标签,并且Input
有一个事件侦听器来侦听输入更改,并使用输入值来获取github用户的名字和p
将在页面上显示名称。
在以下情况下可能会出现比赛条件:
jack
,因为这是用户第一次输入jack
,因此不会缓存结果。将要求获取该jack
用户的Github个人资料david
,因为这也是用户第一次键入david
,因此不会缓存结果。将要求获取该david
用户的Github个人资料jack
。将发出no请求,我们可以从sessionStorage中读取用户个人资料并立即呈现结果。然后,您可以想象,如果第二个请求(即请求提取david
的配置文件的时间过长),则用户将看到david
最终是页面上呈现的最终结果,即使他/她的最后搜索是jack
。这是因为jack
的结果被david
的结果所覆盖,这需要更长的时间才能返回。
在我的示例中,我使用此功能来模拟用户键入
async function userTyping() {
sessionStorage.clear();
inputEl.value = "jack";
inputEl.dispatchEvent(new Event("input"));
await sleep(100);
inputEl.value = "david";
inputEl.dispatchEvent(new Event("input"));
await sleep(100);
inputEl.value = "jack";
inputEl.dispatchEvent(new Event("input"));
}
sleep
函数定义为
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
现在我能想到的是使用反跳来避免用户输入速度太快的情况。但是,它不能从根本上解决问题。
我们还可以使用一些全局变量来跟踪最新的输入值,并使用该变量检查我们将要呈现的结果是否来自最新的输入值。不知何故,我只是认为这不是解决此问题的理想方法。
任何建议都值得赞赏。
答案 0 :(得分:1)
您可以将当前e.target.value
保存在输入处理程序内的变量中。然后,一旦cachedFetch
响应返回,请检查输入字段中是否仍存在相同的值。仅在值匹配时设置输入字段。
(如果值不匹配,例如输入为a
,然后是b
,然后是a
,则b
请求会花费更长的时间完成后,b
将存储在缓存中,但不会显示给用户)
此外,请确保仅在未发生错误时才向用户显示结果:
inputEl.addEventListener("input", e => {
const { value } = e.target;
if (value === "") {
return;
}
const url = endpoint + value;
cachedFetch(url)
.then(response => response.json())
.then((result) => {
if (e.target.value === value) {
resultContainer.innerHTML = result.name;
}
})
.catch(errorHandler);
});
答案 1 :(得分:1)
您也许可以使用AbortController。这是实验性的,尚未添加到所有浏览器中(IE中缺少)。
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
创建一个AbortController实例。
const controller = new AbortController();
const signal = controller.signal;
并将其连接到您的抓取中。
return fetch(url, { ...options, signal }).then(async response => ...
然后从缓存中返回某些内容时取消请求。
if (cached !== null) {
controller.abort();
...
}