HTTP-запрос в Java-скрипте является обязательным способом связи между клиентской и серверной сторонами.

в Java-скрипте мы можем сделать HTTP-запрос, используя скрипт перенаправления, и мы можем запустить запрос в фоновом режиме без какого-либо обновления

чтобы сделать это без обновления, нам нужно запустить запрос XHR или Ajax, как и большинство фреймворков, вызывающих

существует два API-интерфейса Java-скрипта для запуска методов запроса XMLHttpRequest и FETCH.

XMLHttpRequest — это самый старый способ запуска HTTP-запроса Javascript,
это класс со многими методами, предоставляющими обратные вызовы для обработки запроса, ответа, заголовков.

FETCH — это функция, которая позволяет нам запускать HTTP-запросы как асинхронные запросы в зависимости от промисов Java-скрипта, а затем возвращать данные и перехватывать обратные вызовы.

Честно говоря, Fetch намного проще, но XMLHttpRequest более мощный и совместим со старыми браузерами, поэтому все Java-скриптовые фреймворки и библиотеки строят свои собственные HTTP-уровни, зависящие от XMLHttpRequest, только как методы Jquery Ajax, поставщики Angular HTTP Client, «Axios ", и другие.

в этой статье я напишу полный слой Java-скрипта es6, чтобы иметь XMLHttpRequest

  1. Я создам класс Java-скрипта с именем Query, этот конструктор класса будет инициализировать три основных свойства,
    новый экземпляр xhr to gold из XMLHttpRequest.
    метод будет нулевым при инициализации и будет содержать запрос тип метода в запросе.
    данные будут нулевыми при инициализации и будут содержать данные запроса, которые будут отправлены в API.
class Query {
   constructor() {
      this.xhr = new XMLHttpRequest();
      this.method = null;
      this.data = null;
   }
}

2. Добавлю сейчас основные остальные Методы постить, ставить, получать и удалять

post = (data) => {}
put = (data) => {}
delete = () => {}
get = () => {}

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

в этом примере я исправлю базовый URL-адрес, но в реальных проектах он должен быть записан в файле конфигурации или env как константа и создать новый метод, взять конечную точку в качестве параметра и объединить базовый URL-адрес с ним в новый свойство под названием uri

setUrl = (uri) => {
   this.uri = `${baseURL}/${uri}`;
}

4. теперь классу запросов нужен новый метод для установки пользовательских заголовков HTTP, таких как Accept-language, Content-type и другие…
поэтому я создам метод для хранения необходимых заголовков в новом свойстве, называемом headers,
и другой метод для применения переданных заголовков перед запуском HTTP-запроса.

setHeaders = (headers) => {
   this.headers = headers;
}
applyHeaders = () => {
   if (!this.headers) {
      this.headers = {
         'Content-Type': 'application/json'
      };
   }
   //TODO add the next header in case Authorization Token Header 
     in case needed
   if (localStorage.getItem('token')){
      this.headers['Authorization'] = 
         localStorage.getItem('token');
   }
   if (this.headers) {
     Object.keys(this.headers).forEach(key => {
        this.xhr.setRequestHeader(key, this.headers[key]);
     });
   }
}

5. теперь класс запроса близок к завершению, поэтому я создам новый метод для выполнения запроса и вызову его внутри функций методов запроса, которые мы создали ранее.

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

этот метод execute будет делать следующее:
1) добавлять значения в метод и свойства данных.
2) создавать новый Promise.
3) открывать запрос xhr.
4) вызвать метод applyHeaders, который мы создали ранее.
5) прослушивать события onloadend и onerror, чтобы возвращать отказы от обещаний в случае неудачных запросов, разрешать ответ в случае успешного ответа и обрабатывать общие ошибки, такие как 401 , 403, 404, 500 и другие…
6) отправить запрос в API

post = (data) => {return this.excute('POST', data);}
put = (data) => {return this.excute('PUT', data);}
delete = () => {return this.excute('DELETE');}
get = () => {return this.excute('GET');}
execute = (method, data) => {
   this.method = method;
   this.data = data;
   return new Promise((resolve, reject) => {
      this.xhr.open(method, this.uri, true);
      this.applyHeaders();
       
      this.xhr.onloadend = () => {
         if (this.xhr.status >= 200 && this.xhr.status < 300) {
            const response = JSON.parse(this.xhr.response);
            return resolve(response);
         } else {
            if (this.xhr.status === 403) {
               // TODO call rfresh token method
            } else {
               return reject(this.xhr);
            }
         }
      }
      this.xhr.onerror = () => {
         return reject(this.xhr);
      }
      this.xhr.send(data ? JSON.stringify(data) : null);
   });
}

6. теперь Query CLass будет действовать как поставщик HTTP и перехватчик одновременно, для достижения этой цели нам нужно добавить последние два метода метода refreshToken и создать очередь, если запросы с ошибкой будут запускаться после обновления токена.

каждый раз, когда мы создаем новый экземпляр Query CLass, мы фактически создаем новый экземпляр XmlHttpRequest, что означает, что в случае неудачного запроса мы можем перезапустить последний экземпляр, который у нас был, поэтому я добавлю новый метод для создания очереди запросов на случай срок действия токена пользователя истек, и сервер вернул код состояния 401 и метод refreshToken для запроса нового токена пользователя с сервера

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

метод Que обновит заголовки HTTP новым токеном и выполнит экземпляр неудачного запроса.

refreshToken = (q) => {
   return new Promise((resolve, reject) => {
      const query = new Query();
      query.setUrl(
         `token/refresh/${localStorage.getItem('refresh')}`
      );
      query.put().then(res => {
         if (res) {
           localStorage.setItem('token', res.content.token);
           localStorage.setItem(
              'refresh',
              res.content.refreshToken
           );
         return resolve({
            q: q,
            token: res.content.token
         });
      }
    });
 });
}
excuteQueue = () => {
   return this.refreshToken(this).then(res => {
      if (res) {
         res.q.headers.Authorization = res.token;
         return res.q.excute(res.q.method, res.q.data);
      }
   });
}

ожидаемое поведение теперь, когда сервер вернет код состояния 401, созданный нами перехватчик автоматически обновит токен доступа пользователя и запустит последний неудачный запрос без каких-либо действий со стороны пользователя, просто выполнит тихое обновление

6. Наконец, мы вызовем метод Que в случае успешного выполнения запроса с кодом состояния 403 или 401.

if (this.xhr.status === 403) {
   return resolve(this.excuteQueue());
}

последний завершенный пример

class Query {
   constructor() {
      this.xhr = new XMLHttpRequest();
      this.method = null;
      this.data = null;
   }
   setUrl = (uri) => {
      this.uri = `${baseURL}/${uri}`;
   }
   setHeaders = (headers) => {
      this.headers = headers;
   }
   applyHeaders = () => {
      if (!this.headers) {
         this.headers = {
            'Content-Type': 'application/json'
         };
      }
      //TODO add the next header in case Authorization Token Header 
      in case needed
      if (localStorage.getItem('token')){
         this.headers['Authorization'] = 
            localStorage.getItem('token');
      }
      if (this.headers) {
        Object.keys(this.headers).forEach(key => {
           this.xhr.setRequestHeader(key, this.headers[key]);
        });
      }
   }
   refreshToken = (q) => {
      return new Promise((resolve, reject) => {
         const query = new Query();
         query.setUrl(
            `token/refresh/${localStorage.getItem('refresh')}`
         );
         query.put().then(res => {
            if (res) {
              localStorage.setItem('token', res.content.token);
              localStorage.setItem(
                 'refresh',
                 res.content.refreshToken
              );
              return resolve({
                q: q,
                token: res.content.token
              });
           }
        });
     });
   }
   excuteQueue = () => {
      return this.refreshToken(this).then(res => {
         if (res) {
            res.q.headers.Authorization = res.token;
            return res.q.excute(res.q.method, res.q.data);
         }
      });
   }
   post = (data) => {return this.excute('POST', data);}
   put = (data) => {return this.excute('PUT', data);}
   delete = () => {return this.excute('DELETE');}
   get = () => {return this.excute('GET');}
   execute = (method, data) => {
      this.method = method;
      this.data = data;
      return new Promise((resolve, reject) => {
         this.xhr.open(method, this.uri, true);
         this.applyHeaders();
       
         this.xhr.onloadend = () => {
           if (this.xhr.status >= 200 && this.xhr.status < 300) {
              const response = JSON.parse(this.xhr.response);
              return resolve(response);
           } else {
              if (this.xhr.status === 403) {
                 return resolve(this.excuteQueue());
              } else {
                 return reject(this.xhr);
              }
           }
         }
         this.xhr.onerror = () => {
            return reject(this.xhr);
         }
         this.xhr.send(data ? JSON.stringify(data) : null);
      });
   }
}

запустить новый запрос

const req = new Query();
req.setUrl('end point name');
req.setHeaders({headers object});
req.post(data)
   .then(res => {
      console.log(res);
   })
   .catch(error => {
      console.log(error);   
   });