Skip to content

Commit

Permalink
[gem] Improve request module
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Mar 11, 2024
1 parent a5cfcae commit fa35987
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 55 deletions.
4 changes: 2 additions & 2 deletions packages/gem-examples/src/console/item.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { html, GemElement } from '@mantou/gem/lib/element';
import { get } from '@mantou/gem/helper/request';
import { request } from '@mantou/gem/helper/request';
import { customElement } from '@mantou/gem/lib/decorators';
import { Time } from 'duoyun-ui/lib/time';
import { ContextMenu } from 'duoyun-ui/elements/contextmenu';
Expand Down Expand Up @@ -42,7 +42,7 @@ type Item = typeof EXAMPLE;
// 模拟真实 API
const fetchList = (args: FetchEventDetail) => {
console.log(args);
return get(`https://jsonplaceholder.typicode.com/users`).then((list: Item[]) => {
return request(`https://jsonplaceholder.typicode.com/users`).then((list: Item[]) => {
list.forEach((e, i) => {
e.updated = new Time().subtract(i + 1, 'd').getTime();
e.id += 10 * (args.page - 1);
Expand Down
175 changes: 122 additions & 53 deletions packages/gem/src/helper/request.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,129 @@
export const defaultReqInit: RequestInit = {
headers: {
'Content-Type': 'application/json',
},
};

export async function request<T>(uri: string, options: RequestInit = {}): Promise<T> {
const res = await fetch(uri, {
...defaultReqInit,
...options,
});
if (res.status === 0) throw new Error('Request fail');
if (res.status >= 500) throw new Error(res.statusText);
try {
const data = await res.json();
if (res.status >= 400) throw new (Error as any)(data?.message || res.statusText, { cause: data });
return data;
} catch {
throw new Error('Server error');
}
}
import { GemError } from '../lib/utils';

type ReqBody = Record<string, unknown> | BodyInit;
type ReqParams = Record<string, string | number>;
const TypedArray = Object.getPrototypeOf(Object.getPrototypeOf(new Int8Array())).constructor;

function serializationBody(body?: ReqBody): BodyInit | undefined {
if (
body instanceof FormData ||
body instanceof Blob ||
body instanceof TypedArray ||
body instanceof URLSearchParams ||
body instanceof ReadableStream ||
body instanceof ArrayBuffer
) {
return body as BodyInit;
}
if (typeof body === 'object') {
return JSON.stringify(body);
}
return body;
}
type Options = {
origin?: string;
base?: string;
// only for get/del/put/post
appendHeaders?: (input?: any) => HeadersInit;
transformInput?: (input?: any) => any;
transformOutput?: (output: any, req: Request) => any;
} & RequestInit;

function serializationUrl(url: string, params?: Record<string, string | number>) {
const { origin, pathname, search } = new URL(url, location.origin);
const query = new URLSearchParams(params as Record<string, string>).toString();
return `${origin}${pathname}${search ? `${search}&` : query ? '?' : ''}${query}`;
}
function initRequest(options: Options = {}) {
const {
origin = location.origin,
base = '',
appendHeaders = () => ({}),
transformInput = (p?: any) => p,
transformOutput = (p?: any) => p,
...reqInit
} = options;

export function get<T>(url: string, params?: Record<string, string | number>) {
return request<T>(serializationUrl(url, params), { method: 'GET' });
}
export function del<T>(url: string, params?: Record<string, string | number>) {
return request<T>(serializationUrl(url, params), { method: 'DELETE' });
}
export function put<T>(url: string, body?: ReqBody) {
return request<T>(url, { method: 'PUT', body: serializationBody(body) });
}
export function post<T>(url: string, body?: ReqBody) {
return request<T>(url, { method: 'POST', body: serializationBody(body) });
const serializationBody = (body?: ReqBody): BodyInit | undefined => {
if (
body instanceof FormData ||
body instanceof Blob ||
body instanceof TypedArray ||
body instanceof URLSearchParams ||
body instanceof ReadableStream ||
body instanceof ArrayBuffer
) {
return body as BodyInit;
}
if (typeof body === 'object') {
return JSON.stringify(body);
}
return body;
};

const serializationUrl = (path: string, params?: ReqParams) => {
if (new URL(path, location.origin).pathname !== path) {
throw new GemError('`path` format is incorrect');
}
const query = new URLSearchParams(params as Record<string, string>).toString();
return `${origin}${base}${path}${query ? '?' : ''}${query}`;
};

const serializationHeaders = (headers?: HeadersInit) => {
if (!headers) return {};
// auto `toLowerCase`
if (headers instanceof Headers) return Object.fromEntries(headers);
const entries = Array.isArray(headers) ? headers : Object.entries(headers);
return Object.fromEntries(entries.map(([key, value]) => [key.toLowerCase(), value]));
};

const request = async <T>(uri: string, reqOptions: RequestInit = {}): Promise<T> => {
const req = new Request(uri, {
...reqInit,
...reqOptions,
headers: new Headers({
'content-type': 'application/json',
...serializationHeaders(reqInit.headers),
...serializationHeaders(reqOptions.headers),
}),
});
const res = await fetch(req);

if (res.status === 0) throw new Error('Request fail');
if (res.status >= 500) throw new Error(res.statusText);

const contentType = req.headers.get('content-type')!;
const isClientError = res.status >= 400;
try {
let data: any;
if (contentType.includes('json')) {
data = await res.json();
} else if (contentType.includes('octet-stream')) {
data = await res.arrayBuffer();
} else {
data = await res.text();
}
if (isClientError) throw new (Error as any)(res.statusText, { cause: data });
return transformOutput(data, req);
} catch {
throw new Error('Parse error');
}
};

return {
request,
get<T>(path: string, params?: ReqParams) {
const i = transformInput(params);
return request<T>(serializationUrl(path, i), {
method: 'GET',
headers: appendHeaders(i),
});
},
del<T>(path: string, params?: ReqParams) {
const i = transformInput(params);
return request<T>(serializationUrl(path, i), {
method: 'DELETE',
headers: appendHeaders(i),
});
},
put<T>(path: string, body?: ReqBody) {
const i = transformInput(body);
return request<T>(serializationUrl(path), {
method: 'PUT',
headers: appendHeaders(i),
body: serializationBody(i),
});
},
post<T>(path: string, body?: ReqBody) {
const i = transformInput(body);
return request<T>(serializationUrl(path), {
method: 'POST',
headers: appendHeaders(i),
body: serializationBody(i),
});
},
};
}

const { request, del, get, post, put } = initRequest();

export { initRequest, request, del, get, post, put };

0 comments on commit fa35987

Please sign in to comment.