requests.ts
· 3.0 KiB · TypeScript
原始文件
export enum Method {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
}
export type ResponseInterceptor = (r: Response, rq?: RequestInit) => void;
export interface TResponse<T> {
status: number;
error: boolean;
data: T;
response: Response;
}
export type RequestArgs<T> = {
url: string;
body?: T;
data?: FormData;
headers?: Record<string, string>;
};
export class Requests {
private headers: Record<string, string> = {};
private responseInterceptors: ResponseInterceptor[] = [];
private bearer: () => string | null = () => null;
withResponseInterceptor(interceptor: ResponseInterceptor) {
this.responseInterceptors.push(interceptor);
return this;
}
withBearer(bearer: () => string | null) {
this.bearer = bearer;
return this;
}
private callResponseInterceptors(response: Response, request?: RequestInit) {
this.responseInterceptors.forEach(i => i(response, request));
}
constructor(headers: Record<string, string> = {}) {
this.headers = headers;
}
public get<T>(args: RequestArgs<T>): Promise<TResponse<T>> {
return this.do<T>(Method.GET, args);
}
public post<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> {
return this.do<U>(Method.POST, args);
}
public put<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> {
return this.do<U>(Method.PUT, args);
}
public delete<T>(args: RequestArgs<T>): Promise<TResponse<T>> {
return this.do<T>(Method.DELETE, args);
}
public patch<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> {
return this.do<U>(Method.PATCH, args);
}
private methodSupportsBody(method: Method): boolean {
return method === Method.POST || method === Method.PUT || method === Method.PATCH;
}
private async do<T>(method: Method, rargs: RequestArgs<unknown>): Promise<TResponse<T>> {
const payload: RequestInit = {
method,
headers: {
...rargs.headers,
...this.headers,
} as Record<string, string>,
};
if (this.methodSupportsBody(method)) {
if (rargs.data) {
payload.body = rargs.data;
} else {
// @ts-expect-error - we know that the header is there
payload.headers["Content-Type"] = "application/json";
payload.body = JSON.stringify(rargs.body);
}
}
const maybeBearer = this.bearer();
if (maybeBearer) {
// @ts-expect-error - we know that the header is there
payload.headers.Authorization = `Bearer ${maybeBearer}`;
}
const response = await fetch(rargs.url, payload);
this.callResponseInterceptors(response, payload);
const data: T = await (async () => {
if (response.status === 204) {
return {} as T;
}
if (response.headers.get("Content-Type")?.startsWith("application/json")) {
try {
return await response.json();
} catch (e) {
return {} as T;
}
}
return response.body as unknown as T;
})();
return {
status: response.status,
error: !response.ok,
data,
response,
};
}
}
| 1 | export enum Method { |
| 2 | GET = "GET", |
| 3 | POST = "POST", |
| 4 | PUT = "PUT", |
| 5 | DELETE = "DELETE", |
| 6 | PATCH = "PATCH", |
| 7 | } |
| 8 | |
| 9 | export type ResponseInterceptor = (r: Response, rq?: RequestInit) => void; |
| 10 | |
| 11 | export interface TResponse<T> { |
| 12 | status: number; |
| 13 | error: boolean; |
| 14 | data: T; |
| 15 | response: Response; |
| 16 | } |
| 17 | |
| 18 | export type RequestArgs<T> = { |
| 19 | url: string; |
| 20 | body?: T; |
| 21 | data?: FormData; |
| 22 | headers?: Record<string, string>; |
| 23 | }; |
| 24 | |
| 25 | export class Requests { |
| 26 | private headers: Record<string, string> = {}; |
| 27 | private responseInterceptors: ResponseInterceptor[] = []; |
| 28 | private bearer: () => string | null = () => null; |
| 29 | |
| 30 | withResponseInterceptor(interceptor: ResponseInterceptor) { |
| 31 | this.responseInterceptors.push(interceptor); |
| 32 | return this; |
| 33 | } |
| 34 | |
| 35 | withBearer(bearer: () => string | null) { |
| 36 | this.bearer = bearer; |
| 37 | return this; |
| 38 | } |
| 39 | |
| 40 | private callResponseInterceptors(response: Response, request?: RequestInit) { |
| 41 | this.responseInterceptors.forEach(i => i(response, request)); |
| 42 | } |
| 43 | |
| 44 | constructor(headers: Record<string, string> = {}) { |
| 45 | this.headers = headers; |
| 46 | } |
| 47 | |
| 48 | public get<T>(args: RequestArgs<T>): Promise<TResponse<T>> { |
| 49 | return this.do<T>(Method.GET, args); |
| 50 | } |
| 51 | |
| 52 | public post<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> { |
| 53 | return this.do<U>(Method.POST, args); |
| 54 | } |
| 55 | |
| 56 | public put<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> { |
| 57 | return this.do<U>(Method.PUT, args); |
| 58 | } |
| 59 | |
| 60 | public delete<T>(args: RequestArgs<T>): Promise<TResponse<T>> { |
| 61 | return this.do<T>(Method.DELETE, args); |
| 62 | } |
| 63 | |
| 64 | public patch<T, U>(args: RequestArgs<T>): Promise<TResponse<U>> { |
| 65 | return this.do<U>(Method.PATCH, args); |
| 66 | } |
| 67 | |
| 68 | private methodSupportsBody(method: Method): boolean { |
| 69 | return method === Method.POST || method === Method.PUT || method === Method.PATCH; |
| 70 | } |
| 71 | |
| 72 | private async do<T>(method: Method, rargs: RequestArgs<unknown>): Promise<TResponse<T>> { |
| 73 | const payload: RequestInit = { |
| 74 | method, |
| 75 | headers: { |
| 76 | ...rargs.headers, |
| 77 | ...this.headers, |
| 78 | } as Record<string, string>, |
| 79 | }; |
| 80 | |
| 81 | if (this.methodSupportsBody(method)) { |
| 82 | if (rargs.data) { |
| 83 | payload.body = rargs.data; |
| 84 | } else { |
| 85 | // @ts-expect-error - we know that the header is there |
| 86 | payload.headers["Content-Type"] = "application/json"; |
| 87 | payload.body = JSON.stringify(rargs.body); |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | const maybeBearer = this.bearer(); |
| 92 | if (maybeBearer) { |
| 93 | // @ts-expect-error - we know that the header is there |
| 94 | payload.headers.Authorization = `Bearer ${maybeBearer}`; |
| 95 | } |
| 96 | |
| 97 | const response = await fetch(rargs.url, payload); |
| 98 | this.callResponseInterceptors(response, payload); |
| 99 | |
| 100 | const data: T = await (async () => { |
| 101 | if (response.status === 204) { |
| 102 | return {} as T; |
| 103 | } |
| 104 | |
| 105 | if (response.headers.get("Content-Type")?.startsWith("application/json")) { |
| 106 | try { |
| 107 | return await response.json(); |
| 108 | } catch (e) { |
| 109 | return {} as T; |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | return response.body as unknown as T; |
| 114 | })(); |
| 115 | |
| 116 | return { |
| 117 | status: response.status, |
| 118 | error: !response.ok, |
| 119 | data, |
| 120 | response, |
| 121 | }; |
| 122 | } |
| 123 | } |
| 124 |