Выполнение HTTP-запросов с помощью Axios в TypeScript
20 октября 2022 г.
Пару дней назад в моем текущем рабочем проекте мы решили переписать наш простой сервис выборки в более мощное решение с возможностью отмены запросов, которые были отправлены ранее. С самого начала мы решили использовать библиотеку axios
и TypeScript
, так как у всех уже был опыт работы с ним, и он предоставляет решение для отмены запросов на основе промисов. Он уже работает, но в ближайшем будущем будет расширен.
Итак, начнем. Во-первых, нам нужно создать сервис Axios. Все наши сервисы в текущем проекте основаны на классах, поэтому нам нужно создать класс AxiosService
. Существует два основных подхода к использованию Axios: напрямую использовать объект axios
из импорта или создать новый экземпляр с помощью axios.create
. Мы будем использовать последний. Сервис будет иметь 2 поля: экземпляр, который имеет тип AxiosInstance
и cancelToken
, который имеет тип CancelTokenStatic
:
class AxiosService {
instance: AxiosInstance;
cancelTokenStatic: CancelTokenStatic;
}
Затем в конструкторе мы создаем axiosInstanse
:
constructor(url: string) {
this.instance = axios.create({
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
baseURL: url
});
this.cancelTokenStatic = axios.CancelToken;
}
Функция axios.create
может принимать объект с параметрами. В нашем случае мы установили поле headers
и сказали, что наш экземпляр будет работать с json
и предоставить базовый URL-адрес, который будет использоваться для каждого запроса. axios.CancelToken
обеспечивает логику отмены запросов, поэтому мы также сохраняем ее
Еще одна вещь, которая нам нужна, это создать общедоступную функцию, которая будет обновлять baseURL
. Нам это нужно для некоторых конкретных бизнес-кейсов:
setBaseURL(url: string): void {
this.instance.defaults.baseURL = url;
}
Полная версия класса AxiosService
:
import axios, { AxiosInstance, CancelTokenStatic } from 'axios';
class AxiosService {
instance: AxiosInstance;
cancelTokenStatic: CancelTokenStatic;
constructor(url: string) {
this.instance = axios.create({
transformRequest: [
(data, headers): string => {
headers['Authorization'] = `Bearer ${authService.getToken()}`;
return JSON.stringify(data);
},
],
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
baseURL: url,
});
this.cancelTokenStatic = axios.CancelToken;
}
setBaseURL(url: string): void {
this.instance.defaults.baseURL = url;
}
}
Следующим шагом является создание HttpService
, который будет реализовывать всю остальную логику с запросами. Почему нам нужно создать новый сервис, а не просто добавить логику запроса в AxiosService? Потому что это поможет нам использовать любую другую оставшуюся библиотеку вместо Axios, если мы захотим сделать это в будущем.
HttpService будет иметь 3 поля:
class HttpService {
private axiosInstance: AxiosInstance;
private sourceMap: { [key: string]: CancelTokenSource[] } = {};
private cancelToken: CancelTokenStatic;
}
axiosInstance
и cancelToken
мы установим в конструкторе, sourceMap
будет нашим хранилищем, где мы будем хранить токены отмены для запросов
constructor(instance: AxiosInstance, cancelToken: CancelTokenStatic) {
this.axiosInstance = instance;
this.cancelToken = cancelToken;
}
Теперь давайте реализуем логику отмены запросов.
private createCancelToken = (cancelKey: string): CancelToken => {
const source = this.cancelToken.source();
if (this.sourceMap[cancelKey]) {
this.sourceMap[cancelKey].push(source);
} else {
this.sourceMap[cancelKey] = [source];
}
return source.token;
};
Приватная функция createCancelToken
сначала создает новый источник токена отмены. Затем мы проверяем, есть ли в нашем sourceMap
поле с ключом cancelKey
, затем мы просто добавляем новый cancelSource
в массив или создаем новое поле в sourceMap
, а источник является первым элементом в массиве.
Следующим шагом является создание метода cancelPreviousRequests
.
cancelPreviousRequests = (cancelKey: string): void => {
const requests = this.sourceMap[cancelKey] || [];
requests.forEach((item) => {
item.cancel('cancel');
});
this.sourceMap[cancelKey] = [];
};
Мы берем cancelKey
из аргумента и находим такое поле в sourceMap
. Затем мы перебираем в нем каждый CancelTokenSource
и вызываем функцию cancel
. А затем удалите целевое поле в sourceMap
, сохранив в нем пустой массив.
Теперь мы можем начать с логики запросов. Мы будем использовать общий метод sendRequest
для всех запросов. Этот метод будет принимать некоторые параметры. Определим для них интерфейс:
interface RequestOptions {
url: string; // url for request
method?: Method; // HTTP method
params?: Dictionary<any> | string; // request params
cancelKey?: string; // key for sourceMap if request can be canceled
responseType?: ResponseType; // type for response
}
И метод sendRequest
, который вернет Promise:
sendRequest<T = unknown>({
url,
method = 'GET',
params,
cancelKey,
responseType,
}: RequestOptions): Promise<AxiosResponse<T> | void> {
let cancelToken;
if (cancelKey) {
cancelToken = this.createCancelToken(cancelKey);
}
switch (method) {
case 'POST':
return this.postRequest({ url, data: params, cancelToken });
default:
return this.getRequest({
url,
cancelToken,
responseType,
});
}
}
Мы используем универсальный для возвращаемого типа для ответа и метод, который мы установили на GE
T по умолчанию. Во-первых, мы проверяем, есть ли у нас cancelKey
, затем нам нужно создать cancelToken
и отправить его в sourceMap
. Затем мы используем конструкцию switch для перебора методов запроса и вызова соответствующих функций.
Функция запроса также будет принимать некоторые параметры, поэтому нам нужно определить для них интерфейс:
type RequestParams = {
url: string;
cancelToken?: CancelToken;
data?: Dictionary<any> | string;
};
У нас есть 2 базовых метода HTTP для запросов POST и GET:
private async postRequest({
url,
data,
cancelToken,
}: RequestParams): Promise<AxiosResponse | void> {
try {
return await this.axiosInstance.post(url, data, { cancelToken });
} catch (error) {
return Promise.reject(error);
}
}
private async getRequest({
url,
cancelToken,
}: RequestParams): Promise<AxiosResponse | void> {
try {
return await this.axiosInstance.get(url, { cancelToken });
} catch (error) {
return Promise.reject(error);
}
}
Здесь нет ничего особенного. Мы просто вызываем соответствующий запрос из axiosInstance
с нашими параметрами
Полный код для HttpService:
import {
AxiosResponse,
Method,
CancelTokenSource,
CancelToken,
AxiosInstance,
CancelTokenStatic,
} from 'axios';
import { axiosService } from './AxiosService';
interface RequestOptions {
url: string;
method?: Method;
params?: Dictionary<any> | string;
cancelKey?: string;
}
interface RequestParams {
url: string;
cancelToken?: CancelToken;
data?: Dictionary<any> | string;
}
class HttpService {
private axiosInstance: AxiosInstance;
private sourceMap: { [key: string]: CancelTokenSource[] } = {};
private cancelToken: CancelTokenStatic;
constructor(instance: AxiosInstance, cancelToken: CancelTokenStatic) {
this.axiosInstance = instance;
this.cancelToken = cancelToken;
}
private createCancelToken = (cancelKey: string): CancelToken => {
const source = this.cancelToken.source();
if (this.sourceMap[cancelKey]) {
this.sourceMap[cancelKey].push(source);
} else {
this.sourceMap[cancelKey] = [source];
}
return source.token;
};
cancelPreviousRequests = (cancelKey: string): void => {
const requests = this.sourceMap[cancelKey] || [];
requests.forEach((item) => {
item.cancel('cancel');
});
this.sourceMap[cancelKey] = [];
};
private async postRequest({
url,
data,
cancelToken,
}: RequestParams): Promise<AxiosResponse | void> {
try {
return await this.axiosInstance.post(url, data, { cancelToken });
} catch (error) {
return Promise.reject(error);
}
}
private async getRequest({ url, cancelToken }: RequestParams): Promise<AxiosResponse | void> {
try {
return await this.axiosInstance.get(url, { cancelToken });
} catch (error) {
return Promise.reject(error);
}
}
sendRequest<T = unknown>({
url,
method = 'GET',
params,
cancelKey,
}: RequestOptions): Promise<AxiosResponse<T> | void> {
let cancelToken;
if (cancelKey) {
cancelToken = this.createCancelToken(cancelKey);
}
switch (method) {
case 'POST':
return this.postRequest({ url, data: params, cancelToken });
default:
return this.getRequest({
url,
cancelToken,
});
}
}
Пример использования:
httpService.cancelPreviousRequests('testRequest');
httpService
.sendRequest<string[]>({
url: '/api/testurl',
cancelKey: 'testRequest',
})
.then((response) => {
if (response?.data) {
console.log(response.data)
}
})
.catch((error) => {
console.warn(error);
});
Оригинал