进行缓存的Ajax调用时如何避免竞争状况

时间:2020-04-10 23:20:11

标签: javascript html http caching frontend

由于许多公共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将在页面上显示名称。

在以下情况下可能会出现比赛条件:

  1. 用户在输入字段中输入jack,因为这是用户第一次输入jack,因此不会缓存结果。将要求获取该jack用户的Github个人资料
  2. 然后,用户在输入字段中输入david,因为这也是用户第一次键入david,因此不会缓存结果。将要求获取该david用户的Github个人资料
  3. 最后,由于结果已经在缓存中,因此用户第二次在输入字段中键入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));

现在我能想到的是使用反跳来避免用户输入速度太快的情况。但是,它不能从根本上解决问题。

我们还可以使用一些全局变量来跟踪最新的输入值,并使用该变量检查我们将要呈现的结果是否来自最新的输入值。不知何故,我只是认为这不是解决此问题的理想方法。

任何建议都值得赞赏。

2 个答案:

答案 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();
  ...
}
相关问题