184 lines
4.3 KiB
TypeScript
184 lines
4.3 KiB
TypeScript
import { runInAction } from "mobx";
|
|
|
|
type mobxFetchOptions<RequestType, ResponseType, Store> = {
|
|
store: Store;
|
|
value?: keyof Store;
|
|
values?: Array<keyof Store>;
|
|
loading?: keyof Store;
|
|
error?: keyof Store;
|
|
fn: RequestType extends void
|
|
? (signal?: AbortSignal) => Promise<ResponseType>
|
|
: (request: RequestType, signal?: AbortSignal) => Promise<ResponseType>;
|
|
|
|
pollingInterval?: number;
|
|
resetValue?: boolean;
|
|
transform?: (response: ResponseType) => Partial<Record<string, any>>;
|
|
onSuccess?: (response: ResponseType) => void;
|
|
};
|
|
|
|
type FetchFunction<RequestType, ResponseType> = RequestType extends void
|
|
? {
|
|
(): Promise<ResponseType | null>;
|
|
stopPolling?: () => void;
|
|
}
|
|
: {
|
|
(request: RequestType): Promise<ResponseType | null>;
|
|
stopPolling?: () => void;
|
|
};
|
|
|
|
export function mobxFetch<ResponseType, Store extends Record<string, any>>(
|
|
options: mobxFetchOptions<void, ResponseType, Store>
|
|
): FetchFunction<void, ResponseType>;
|
|
|
|
export function mobxFetch<
|
|
RequestType,
|
|
ResponseType,
|
|
Store extends Record<string, any>,
|
|
>(
|
|
options: mobxFetchOptions<RequestType, ResponseType, Store>
|
|
): FetchFunction<RequestType, ResponseType>;
|
|
|
|
export function mobxFetch<
|
|
RequestType,
|
|
ResponseType,
|
|
Store extends Record<string, any>,
|
|
>(
|
|
options: mobxFetchOptions<RequestType, ResponseType, Store>
|
|
): FetchFunction<RequestType, ResponseType> {
|
|
const {
|
|
store,
|
|
value,
|
|
values,
|
|
loading,
|
|
error,
|
|
fn,
|
|
pollingInterval,
|
|
resetValue,
|
|
transform,
|
|
onSuccess,
|
|
} = options;
|
|
|
|
let abortController: AbortController | undefined;
|
|
let pollingTimer: ReturnType<typeof setInterval> | undefined;
|
|
let currentRequest: RequestType | undefined;
|
|
|
|
const stopPolling = () => {
|
|
if (pollingTimer) {
|
|
clearInterval(pollingTimer);
|
|
pollingTimer = undefined;
|
|
}
|
|
abortController?.abort();
|
|
};
|
|
|
|
const fetch = async (request?: RequestType): Promise<ResponseType | null> => {
|
|
abortController?.abort();
|
|
abortController = new AbortController();
|
|
currentRequest = request as RequestType;
|
|
|
|
runInAction(() => {
|
|
if (value) {
|
|
(store[value] as any) = resetValue ? null : store[value];
|
|
}
|
|
|
|
if (values) {
|
|
values.forEach((key) => {
|
|
(store[key] as any) = resetValue ? null : store[key];
|
|
});
|
|
}
|
|
|
|
if (error) {
|
|
(store[error] as any) = null;
|
|
}
|
|
|
|
if (loading) {
|
|
(store[loading] as any) = true;
|
|
}
|
|
});
|
|
|
|
try {
|
|
const result = await (
|
|
fn as (
|
|
request?: RequestType,
|
|
signal?: AbortSignal
|
|
) => Promise<ResponseType>
|
|
)(request, abortController.signal);
|
|
|
|
runInAction(() => {
|
|
if (values && transform) {
|
|
const transformed = transform(result) as Record<string, any>;
|
|
values.forEach((key) => {
|
|
const k = key as string;
|
|
if (k in transformed) {
|
|
(store[key] as any) = transformed[k];
|
|
}
|
|
});
|
|
} else if (value) {
|
|
(store[value] as any) = result as ResponseType;
|
|
}
|
|
|
|
if (loading) {
|
|
(store[loading] as any) = false;
|
|
}
|
|
|
|
if (error) {
|
|
(store[error] as any) = null;
|
|
}
|
|
});
|
|
|
|
if (pollingInterval && !pollingTimer) {
|
|
pollingTimer = setInterval(() => {
|
|
if (currentRequest !== undefined) {
|
|
fetch(currentRequest);
|
|
} else {
|
|
fetch();
|
|
}
|
|
}, pollingInterval);
|
|
}
|
|
|
|
if (onSuccess) {
|
|
onSuccess(result);
|
|
}
|
|
|
|
return result;
|
|
} catch (err) {
|
|
if (!(err instanceof Error && err.name === "CanceledError")) {
|
|
runInAction(() => {
|
|
if (error) {
|
|
(store[error] as any) =
|
|
err instanceof Error ? err.message : String(err);
|
|
}
|
|
|
|
if (loading) {
|
|
(store[loading] as any) = false;
|
|
}
|
|
|
|
if (value) {
|
|
(store[value] as any) = null;
|
|
}
|
|
|
|
if (values) {
|
|
values.forEach((key) => {
|
|
(store[key] as any) = null;
|
|
});
|
|
}
|
|
});
|
|
|
|
throw err;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const fetchWithStopPolling = fetch as FetchFunction<
|
|
RequestType,
|
|
ResponseType
|
|
>;
|
|
|
|
if (pollingInterval) {
|
|
fetchWithStopPolling.stopPolling = stopPolling;
|
|
}
|
|
|
|
return fetchWithStopPolling;
|
|
}
|