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