Как я могу избежать состояния гонки при совершении кэшированных вызовов Ajax - PullRequest
2 голосов
/ 11 апреля 2020

Поскольку многие API-интерфейсы publi c, такие как API-интерфейс GitHub publi c, имеют ограничение на количество запросов, поэтому имеет смысл реализовать некоторый механизм кэширования, чтобы избежать ненужных вызовов запросов. Однако я обнаружил, что это может повлечь за собой состояние гонки.

Я кодировал пример, чтобы продемонстрировать ситуацию 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 поэтому результат не кэшируется. Будет сделан запрос на выборку этого профиля пользователя Github jack
  2. Затем пользователь вводит david в поле ввода, поскольку пользователь также вводит david впервые, поэтому результат не будет кэшируются. Будет сделан запрос на выборку этого профиля пользователя Github david
  3. Наконец, пользователь вводит jack в поле ввода во второй раз, поскольку результат уже находится в кэше. Запрос не будет сделан, и мы сможем прочитать профиль пользователя из 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, чтобы избежать ситуации, когда пользователь печатает слишком быстро. Однако это не решает проблему на фундаментальном уровне.

Также мы можем использовать некоторую глобальную переменную, чтобы отслеживать последнее входное значение, и использовать ее, чтобы проверить, получен ли результат, который мы собираемся отобразить, последнее входное значение. Почему-то я просто не думаю, что это элегантное решение этой проблемы.

Любые предложения приветствуются.

Ответы [ 2 ]

1 голос
/ 11 апреля 2020

Возможно, вы сможете использовать 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();
  ...
}
1 голос
/ 11 апреля 2020

Вы можете сохранить текущий e.target.value в переменной внутри обработчика ввода. Затем, как только ответ cachedFetch возвращается, проверьте, находится ли то же значение в поле ввода. Устанавливайте поле ввода только в том случае, если значения совпадают.

(Если значения не совпадают, например, если ввод a, затем b, затем a, и он принимает b запросите более длительное время до окончания sh, тогда 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);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...