parent
94e8638d28
commit
28088b270d
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,239 @@
|
||||
import { WatchEventType } from '@/config';
|
||||
import { request } from '@umijs/max';
|
||||
import axios from 'axios';
|
||||
import _ from 'lodash';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface RequestConfig {
|
||||
url: string;
|
||||
handler: (data: any) => any;
|
||||
beforeReconnect?: () => void;
|
||||
params?: object;
|
||||
contentType?: 'json' | 'text';
|
||||
}
|
||||
|
||||
export const sliceJsonStr = (text: string) => {
|
||||
if (!text) return [];
|
||||
const result: string[] = [];
|
||||
for (let i = 0, j = 0, start = 0; i < text.length; i += 1) {
|
||||
if (text.charAt(i) === '{') {
|
||||
j += 1;
|
||||
}
|
||||
if (text.charAt(i) === '}') {
|
||||
j -= 1;
|
||||
}
|
||||
if (j === 0 && i !== start) {
|
||||
result.push(text.slice(start, i + 1));
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const parseJsonStr = (list: string[]) => {
|
||||
return _.map(list, (str: string) => {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
};
|
||||
|
||||
const findMatchingClosingBracket = (inputStr: string, startIndex: number) => {
|
||||
let count = 0;
|
||||
for (let i = startIndex; i < inputStr.length; i += 1) {
|
||||
if (inputStr[i] === '{') {
|
||||
count += 1;
|
||||
} else if (inputStr[i] === '}') {
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if (count === 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const findValidJSONStrings = (inputStr: string) => {
|
||||
const validJSONStrings: any[] = [];
|
||||
let startIndex = 0;
|
||||
|
||||
while (startIndex < inputStr.length) {
|
||||
const openingBraceIndex = inputStr.indexOf('{', startIndex);
|
||||
if (openingBraceIndex === -1) {
|
||||
break; // No more opening braces
|
||||
}
|
||||
|
||||
const closingBraceIndex = findMatchingClosingBracket(
|
||||
inputStr,
|
||||
openingBraceIndex
|
||||
);
|
||||
if (closingBraceIndex === -1) {
|
||||
// If no matching closing brace, set startIndex to next character
|
||||
startIndex = openingBraceIndex + 1;
|
||||
} else {
|
||||
const jsonString = inputStr.substring(
|
||||
openingBraceIndex,
|
||||
closingBraceIndex + 1
|
||||
);
|
||||
try {
|
||||
const data = JSON.parse(jsonString);
|
||||
validJSONStrings.push(data);
|
||||
} catch (error) {
|
||||
// Ignore invalid JSON
|
||||
}
|
||||
|
||||
startIndex = closingBraceIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return validJSONStrings;
|
||||
};
|
||||
|
||||
const parseData = (data: string) => {
|
||||
const res = findValidJSONStrings(data);
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createAxiosToken = () => {
|
||||
const { CancelToken } = axios;
|
||||
const source = CancelToken.source();
|
||||
return source;
|
||||
};
|
||||
|
||||
export const sliceData = (data: string, loaded: number, loadedSize: any) => {
|
||||
const result = data.slice(loadedSize.current);
|
||||
loadedSize.current = loaded;
|
||||
return result;
|
||||
};
|
||||
|
||||
const useSetChunkRequest = () => {
|
||||
const [requestReadyState, setRequestReadyState] = useState(3);
|
||||
const axiosToken = useRef<any>(null);
|
||||
const requestConfig = useRef<any>({});
|
||||
const loaded = useRef(0);
|
||||
const total = useRef(0);
|
||||
const totalCount = 5;
|
||||
const retryCount = useRef(totalCount);
|
||||
const particalConfig = { params: {}, contentType: 'json' };
|
||||
const timer = useRef<any>(null);
|
||||
const loadedSize = useRef(0);
|
||||
|
||||
const reset = () => {
|
||||
loaded.current = 0;
|
||||
total.current = 0;
|
||||
loadedSize.current = 0;
|
||||
setRequestReadyState(3);
|
||||
};
|
||||
|
||||
const resetResultSchema = (result: any[]) => {
|
||||
return _.map(result, (data: any) => {
|
||||
if (data.type === WatchEventType.DELETE) {
|
||||
data.ids = _.map(data.items, (item: any) => item.id);
|
||||
}
|
||||
data.collection = data.items || [];
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
const axiosChunkRequest = async ({
|
||||
url,
|
||||
handler,
|
||||
beforeReconnect,
|
||||
params = {},
|
||||
contentType = 'json'
|
||||
}: RequestConfig) => {
|
||||
reset();
|
||||
axiosToken.current?.cancel?.();
|
||||
axiosToken.current = createAxiosToken();
|
||||
try {
|
||||
const { request: requestData } = await request(url, {
|
||||
params: {
|
||||
...params,
|
||||
watch: true
|
||||
},
|
||||
skipErrorHandler: true,
|
||||
getResponse: true,
|
||||
cancelToken: axiosToken.current.token,
|
||||
|
||||
async onDownloadProgress(e) {
|
||||
const { response, readyState } = e.currentTarget;
|
||||
setRequestReadyState(readyState);
|
||||
loaded.current = e.loaded || 0;
|
||||
total.current = e.total || 0;
|
||||
|
||||
let result = response;
|
||||
let cres = '';
|
||||
if (contentType === 'json') {
|
||||
const currentRes = sliceData(response, e.loaded, loadedSize);
|
||||
result = parseData(currentRes);
|
||||
result = resetResultSchema(result);
|
||||
cres = currentRes;
|
||||
}
|
||||
|
||||
handler(result);
|
||||
console.log('chunkrequest===', {
|
||||
result,
|
||||
url,
|
||||
params,
|
||||
raw: cres
|
||||
});
|
||||
}
|
||||
});
|
||||
setRequestReadyState(requestData?.readyState);
|
||||
if (retryCount.current > 0) {
|
||||
retryCount.current -= 1;
|
||||
}
|
||||
} catch (error) {
|
||||
if (!axios.isCancel(error)) {
|
||||
setRequestReadyState(4);
|
||||
if (retryCount.current > 0) {
|
||||
retryCount.current -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return axiosToken.current;
|
||||
};
|
||||
|
||||
const setChunkRequest = (config: RequestConfig) => {
|
||||
requestConfig.current = { ...particalConfig, ...config };
|
||||
retryCount.current = totalCount;
|
||||
clearTimeout(timer.current);
|
||||
axiosChunkRequest(requestConfig.current);
|
||||
return axiosToken.current;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleUnload = () => {
|
||||
axiosToken.current?.cancel?.();
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleUnload);
|
||||
|
||||
return () => {
|
||||
reset();
|
||||
requestConfig.current.beforeReconnect = null;
|
||||
axiosToken.current?.cancel?.();
|
||||
clearTimeout(timer.current);
|
||||
window.removeEventListener('beforeunload', handleUnload);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (requestReadyState === 4 && retryCount.current > 0) {
|
||||
requestConfig.current.beforeReconnect?.();
|
||||
clearTimeout(timer.current);
|
||||
timer.current = setTimeout(
|
||||
() => {
|
||||
axiosChunkRequest(requestConfig.current);
|
||||
},
|
||||
2 ** (totalCount - retryCount.current) * 1000
|
||||
);
|
||||
}
|
||||
}, [requestReadyState]);
|
||||
|
||||
return {
|
||||
setChunkRequest
|
||||
};
|
||||
};
|
||||
|
||||
export default useSetChunkRequest;
|
||||
@ -0,0 +1,6 @@
|
||||
import axiso from 'axios';
|
||||
|
||||
export default function useRequestToken() {
|
||||
const { source } = axiso.CancelToken;
|
||||
return source;
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
import { WatchEventType } from '@/config';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface ChunkedCollection {
|
||||
ids: string[];
|
||||
collection: any[];
|
||||
type: string;
|
||||
}
|
||||
// Only used to update lists without nested state
|
||||
export function useUpdateChunkedList(
|
||||
dataList: { id: string | number }[],
|
||||
options: {
|
||||
setDataList: (args: any) => void;
|
||||
callback?: (args: any) => void;
|
||||
filterFun?: (args: any) => boolean;
|
||||
mapFun?: (args: any) => any;
|
||||
computedID?: (d: object) => string;
|
||||
}
|
||||
) {
|
||||
const updateChunkedList = (data: ChunkedCollection) => {
|
||||
let collections = data?.collection || [];
|
||||
if (options?.computedID) {
|
||||
collections = _.map(collections, (item: any) => {
|
||||
item.id = options?.computedID?.(item);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
if (options?.filterFun) {
|
||||
collections = _.filter(data?.collection, options?.filterFun);
|
||||
}
|
||||
if (options?.mapFun) {
|
||||
collections = _.map(data?.collection, options?.mapFun);
|
||||
}
|
||||
const ids = data?.ids || [];
|
||||
// CREATE
|
||||
if (data?.type === WatchEventType.CREATE) {
|
||||
_.each(collections, (item: any) => {
|
||||
const updateIndex = _.findIndex(
|
||||
dataList,
|
||||
(sItem: any) => sItem.id === item.id
|
||||
);
|
||||
if (updateIndex === -1) {
|
||||
const updateItem = _.cloneDeep(item);
|
||||
const list = _.concat(updateItem, dataList);
|
||||
options.setDataList(list);
|
||||
}
|
||||
});
|
||||
}
|
||||
// DELETE
|
||||
if (data?.type === WatchEventType.DELETE) {
|
||||
const list = _.filter(dataList, (item: any) => {
|
||||
return !_.find(ids, (id: any) => id === item.id);
|
||||
});
|
||||
options.setDataList(list);
|
||||
}
|
||||
// UPDATE
|
||||
if (data?.type === WatchEventType.UPDATE) {
|
||||
_.each(collections, (item: any) => {
|
||||
const updateIndex = _.findIndex(
|
||||
dataList,
|
||||
(sItem: any) => sItem.id === item.id
|
||||
);
|
||||
if (updateIndex > -1) {
|
||||
const updateItem = _.cloneDeep(item);
|
||||
dataList[updateIndex] = updateItem;
|
||||
}
|
||||
options.setDataList(dataList);
|
||||
});
|
||||
}
|
||||
if (options?.callback) {
|
||||
options?.callback(dataList);
|
||||
}
|
||||
};
|
||||
return {
|
||||
updateChunkedList
|
||||
};
|
||||
}
|
||||
|
||||
export default useUpdateChunkedList;
|
||||
Loading…
Reference in new issue