/* eslint-disable @typescript-eslint/ban-ts-comment */
|
import { reactive } from 'vue';
|
|
import type { FetchState, PluginReturn, Service, Subscribe, UseRequestOptions } from './types';
|
import { isFunction } from './utils/isFunction';
|
|
export default class Fetch<TData, TParams extends any[]> {
|
pluginImpls: PluginReturn<TData, TParams>[] = [];
|
|
count: number = 0;
|
|
state: FetchState<TData, TParams> = reactive({
|
loading: false,
|
params: undefined,
|
data: undefined,
|
error: undefined,
|
});
|
|
constructor(
|
public serviceRef: Service<TData, TParams>,
|
public options: UseRequestOptions<TData, TParams>,
|
public subscribe: Subscribe,
|
public initState: Partial<FetchState<TData, TParams>> = {},
|
) {
|
this.setState({ loading: !options.manual, ...initState });
|
}
|
|
setState(s: Partial<FetchState<TData, TParams>> = {}) {
|
Object.assign(this.state, s);
|
this.subscribe();
|
}
|
|
runPluginHandler(event: keyof PluginReturn<TData, TParams>, ...rest: any[]) {
|
// @ts-ignore
|
const r = this.pluginImpls.map((i) => i[event]?.(...rest)).filter(Boolean);
|
return Object.assign({}, ...r);
|
}
|
|
async runAsync(...params: TParams): Promise<TData> {
|
this.count += 1;
|
const currentCount = this.count;
|
|
const {
|
stopNow = false,
|
returnNow = false,
|
...state
|
} = this.runPluginHandler('onBefore', params);
|
|
// stop request
|
if (stopNow) {
|
return new Promise(() => {});
|
}
|
|
this.setState({
|
loading: true,
|
params,
|
...state,
|
});
|
|
// return now
|
if (returnNow) {
|
return Promise.resolve(state.data);
|
}
|
|
this.options.onBefore?.(params);
|
|
try {
|
// replace service
|
let { servicePromise } = this.runPluginHandler('onRequest', this.serviceRef, params);
|
|
if (!servicePromise) {
|
servicePromise = this.serviceRef(...params);
|
}
|
|
const res = await servicePromise;
|
|
if (currentCount !== this.count) {
|
// prevent run.then when request is canceled
|
return new Promise(() => {});
|
}
|
|
// const formattedResult = this.options.formatResultRef.current ? this.options.formatResultRef.current(res) : res;
|
|
this.setState({ data: res, error: undefined, loading: false });
|
|
this.options.onSuccess?.(res, params);
|
this.runPluginHandler('onSuccess', res, params);
|
|
this.options.onFinally?.(params, res, undefined);
|
|
if (currentCount === this.count) {
|
this.runPluginHandler('onFinally', params, res, undefined);
|
}
|
|
return res;
|
} catch (error) {
|
if (currentCount !== this.count) {
|
// prevent run.then when request is canceled
|
return new Promise(() => {});
|
}
|
|
this.setState({ error, loading: false });
|
|
this.options.onError?.(error, params);
|
this.runPluginHandler('onError', error, params);
|
|
this.options.onFinally?.(params, undefined, error);
|
|
if (currentCount === this.count) {
|
this.runPluginHandler('onFinally', params, undefined, error);
|
}
|
|
throw error;
|
}
|
}
|
|
run(...params: TParams) {
|
this.runAsync(...params).catch((error) => {
|
if (!this.options.onError) {
|
console.error(error);
|
}
|
});
|
}
|
|
cancel() {
|
this.count += 1;
|
this.setState({ loading: false });
|
|
this.runPluginHandler('onCancel');
|
}
|
|
refresh() {
|
// @ts-ignore
|
this.run(...(this.state.params || []));
|
}
|
|
refreshAsync() {
|
// @ts-ignore
|
return this.runAsync(...(this.state.params || []));
|
}
|
|
mutate(data?: TData | ((oldData?: TData) => TData | undefined)) {
|
const targetData = isFunction(data) ? data(this.state.data) : data;
|
this.runPluginHandler('onMutate', targetData);
|
this.setState({ data: targetData });
|
}
|
}
|