🏎Что такое race conditions в React хуках?
Хуки эффектов, в которых есть запросы с последующей установкой ответа в state могут приводить к race conditions. Race conditions возникает, когда хук выполняется слишком часто, и поскольку очередность ответов от сервера не гарантирована, то более ранние запросы могут завершиться после более поздних и тогда в state будут записаны неверные данные.
Например, вот такой код может привести к проблеме:
useEffect(() => {
fetch(`/api/data?query=${query}`)
.then((res) => res.json())
.then((data) => setData(data));
}, [query]);
Этот эффект будет выполняться при каждом изменении query. Таким образом, при вводе query отправится несколько вопросов, но порядок ответа от сервера и тем самым вызов setState не гарантирован.
Вот здесь я собрала песочницу для демонстрации проблемы.
В этом примере мы видим строку поиска товаров. Я добавила искусственную задержку: чем короче строка поиска, тем дольше выполняется запрос. Попробуйте ввести в поиск слово “dog”. Вы увидите, что сначала отображается один результат (”dog food”), а затем больше и больше результатов.
Почему? Потому что при вводе слова “dog” мы отправляем подряд целых три запроса: запрос с поиском значения “d”, запрос с поиском значения “do” и наконец запрос с поиском значения “dog”. Я реализовала искусственную задержку - чем больше букв в поисковой строке, тем быстрее выполнится запрос. Поэтому ответы нам будут приходить в обратном порядке: сначала ответ для “dog”, потом для “do” и наконец для “d”. Хук setData сработает в порядке возвращения ответов от сервера: сначала установятся значения для “dog”, потом для “do” и наконец для “d”. В этом примере задержка искусственная, но реальный сервер тоже не гарантирует порядок ответов и более ранний запрос может завершится после более позднего. В таком мы установим в state устаревшее состояние.
🪄Как предотвратить race conditions?
Проблему решает AbortController: с его помощью можно отменить предыдущий ответ таким образом, что браузер будет дожидаться ответа только для последнего запроса. Это нативное решение и оно подходит в абсолютном большинстве случаев.
useEffect(() => {
const controller = new AbortController();
fetch(`/api/data?query=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => controller.abort(); // отменяем прошлый запрос
}, [query]);
#react
Хуки эффектов, в которых есть запросы с последующей установкой ответа в state могут приводить к race conditions. Race conditions возникает, когда хук выполняется слишком часто, и поскольку очередность ответов от сервера не гарантирована, то более ранние запросы могут завершиться после более поздних и тогда в state будут записаны неверные данные.
Например, вот такой код может привести к проблеме:
useEffect(() => {
fetch(`/api/data?query=${query}`)
.then((res) => res.json())
.then((data) => setData(data));
}, [query]);
Этот эффект будет выполняться при каждом изменении query. Таким образом, при вводе query отправится несколько вопросов, но порядок ответа от сервера и тем самым вызов setState не гарантирован.
Вот здесь я собрала песочницу для демонстрации проблемы.
В этом примере мы видим строку поиска товаров. Я добавила искусственную задержку: чем короче строка поиска, тем дольше выполняется запрос. Попробуйте ввести в поиск слово “dog”. Вы увидите, что сначала отображается один результат (”dog food”), а затем больше и больше результатов.
Почему? Потому что при вводе слова “dog” мы отправляем подряд целых три запроса: запрос с поиском значения “d”, запрос с поиском значения “do” и наконец запрос с поиском значения “dog”. Я реализовала искусственную задержку - чем больше букв в поисковой строке, тем быстрее выполнится запрос. Поэтому ответы нам будут приходить в обратном порядке: сначала ответ для “dog”, потом для “do” и наконец для “d”. Хук setData сработает в порядке возвращения ответов от сервера: сначала установятся значения для “dog”, потом для “do” и наконец для “d”. В этом примере задержка искусственная, но реальный сервер тоже не гарантирует порядок ответов и более ранний запрос может завершится после более позднего. В таком мы установим в state устаревшее состояние.
🪄Как предотвратить race conditions?
Проблему решает AbortController: с его помощью можно отменить предыдущий ответ таким образом, что браузер будет дожидаться ответа только для последнего запроса. Это нативное решение и оно подходит в абсолютном большинстве случаев.
useEffect(() => {
const controller = new AbortController();
fetch(`/api/data?query=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => controller.abort(); // отменяем прошлый запрос
}, [query]);
#react