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
- Я создам класс 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); });