Как избежать дублирующихся запросов при обновлении токена в JavaScript

Когда несколько запросов к API одновременно получают ошибку авторизации, все они могут запустить обновление токена. Если не контролировать этот процесс, можно отправить несколько одинаковых запросов на сервер. Разберём, как этого избежать с помощью простого решения, которое подойдет в ваш проект будь он на Vue, React или любом JavaScript/TypeScript фреймворке.

Проблема параллельных запросов на обновление

Представим ситуацию: несколько частей интерфейса (например, хедер, сайдбар и контент) отправляют запросы к API для загрузки данных.

Как избежать дублирующихся запросов при обновлении токена в JavaScript

Если токен истёк, все они получат ошибку авторизации и попробуют обновить токен. Без контроля это приведёт к множеству идентичных запросов на refresh, хотя на самом деле нужен только один. Чтобы этого избежать, используется следующий подход:

let result = null; const refresh = () => { if (result) return result; // tokenService.refresh() - тут делаем запрос на рефреш // и обновляем токен в нашем сторе result = tokenService.refresh().finally(() => { result = null; }); return result; };

Эта функция предотвращает дублирующиеся вызовы refresh(), возвращая один и тот же Promise для всех одновременных вызовов, пока обновление не завершится. После завершения запроса переменная result обнуляется, позволяя запустить обновление снова при следующем вызове.

Что происходит при нескольких запросах?

Рассмотрим ситуацию, когда несколько асинхронных функций делают запросы и после них вызывают refresh():

async function fetchData() { await fetch("https://api.example.com/data"); await refresh(); } fetchData(); fetchData(); fetchData();

Здесь три вызова fetchData() отправляют запросы к серверу и затем вызывают refresh(). Разберём последовательность выполнения в контексте стека вызовов, очереди микрозадач и очереди макрозадач:

  • fetchData() вызывается три раза подряд. В каждом из них fetch() отправляет запрос в фоновый процесс (Web API) и выходит из стека вызовов.

  • После await fetch(), выполнение fetchData() приостанавливается до получения ответа, а основной поток продолжает работу.

  • Когда сервер отвечает, обработчик fetch().then() добавляется в очередь микрозадач (и за ней все что после каждого await).

  • Вызывается refresh(), видит result === null и создаёт Promise, отправляя запрос на обновление токена.

  • Второй и третий fetchData() тоже доходят до refresh(), но теперь result !== null, поэтому они получают уже созданный Promise.

  • Когда запрос на обновление токена завершается, его обработчик .finally() добавляется в очередь микрозадач, где result сбрасывается в null

Заключение

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

Это моя первая статья, если у вас есть альтернативные или похожие решения, пишите в комментариях. Буду рад любой критике!

Начать дискуссию