【发布时间】:2020-07-23 18:43:36
【问题描述】:
由于很多公共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));
现在我能想到的是使用 debounce 来避免用户输入速度过快的情况。但是它并没有解决根本层面的问题。
我们还可以使用一些全局变量来跟踪最新的输入值,并使用它来检查我们要渲染的结果是否来自最新的输入值。不知何故,我只是不认为这是解决这个问题的优雅解决方案。
欢迎提出任何建议。
【问题讨论】:
-
返回查询和响应。然后检查查询是否与当前输入值匹配,如果匹配则只呈现响应。
-
@Barmar 您能否通过编写解决方案或解决方案的某些部分来详细说明解决方案?不确定我是否理解您所说的“将查询与响应一起返回”的意思
-
我认为CertainPerformance 的回答与我的想法一致。
标签: javascript html http caching frontend