Skip to content

Commit

Permalink
* move the timing for pending fetch into watchOnce() @ fetchPosts()
Browse files Browse the repository at this point in the history
- `onBeforeRouteUpdate()` no longer waits for `fetchPosts()` & `parseRouteThenFetch()` to return fetch result before updating route
@ `<Post>`
* fix importing duplicated types `ApiStatus` @ `<Status>`

* rename function `getRequester(WithReCAPTCHA)()` to `queryFunction(WithReCAPTCHA)()`
* rename type `ReqesuterGetter` to `QueryFunctions`
* rename param `requesterGetter` to `queryFn` @ `useApi*()`
* rename function `reCAPTCHACheck()` to `checkReCAPTCHA()`
@ api/index.ts
@ fe
  • Loading branch information
n0099 committed Feb 21, 2024
1 parent 61b7973 commit c7571bb
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 43 deletions.
38 changes: 21 additions & 17 deletions fe/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const throwIfApiError = <TResponse>(response: ApiError | TResponse): TRes

return response;
};
export const getRequester = <TResponse, TQueryParam>(endpoint: string, queryParam?: TQueryParam) =>
export const queryFunction = <TResponse, TQueryParam>(endpoint: string, queryParam?: TQueryParam) =>
async (queryContext: QueryFunctionContext): Promise<TResponse> => {
nprogress.start();
document.body.style.cursor = 'progress';
Expand All @@ -51,7 +51,7 @@ export const getRequester = <TResponse, TQueryParam>(endpoint: string, queryPara
document.body.style.cursor = '';
}
};
const reCAPTCHACheck = async (action = '') =>
const checkReCAPTCHA = async (action = '') =>
new Promise<{ reCAPTCHA?: string }>((reslove, reject) => {
if (import.meta.env.VITE_RECAPTCHA_SITE_KEY === '') {
reslove({});
Expand All @@ -67,43 +67,47 @@ const reCAPTCHACheck = async (action = '') =>
});
}
});
const getRequesterWithReCAPTCHA = <TResponse, TQueryParam>
const queryFunctionWithReCAPTCHA = <TResponse, TQueryParam>
(endpoint: string, queryParam?: TQueryParam, action = '') =>
async (queryContext: QueryFunctionContext): Promise<TResponse> =>
getRequester<TResponse, TQueryParam & { reCAPTCHA?: string }>(
queryFunction<TResponse, TQueryParam & { reCAPTCHA?: string }>(
endpoint,
{ ...queryParam as TQueryParam, ...await reCAPTCHACheck(action) }
{ ...queryParam as TQueryParam, ...await checkReCAPTCHA(action) }
)(queryContext);

export type ApiErrorClass = ApiResponseError | FetchResponseError;
type ReqesuterGetter = typeof getRequester | typeof getRequesterWithReCAPTCHA;
type QueryFunctions = typeof queryFunction | typeof queryFunctionWithReCAPTCHA;
const useApi = <
TApi extends Api<TResponse, TQueryParam>,
TResponse = TApi['response'],
TQueryParam = TApi['queryParam']>
(endpoint: string, requesterGetter: ReqesuterGetter) =>
(endpoint: string, queryFn: QueryFunctions) =>
(queryParam?: Ref<TQueryParam | undefined>, enabled?: MaybeRefOrGetter<boolean>) =>
useQuery<TResponse, ApiErrorClass, TResponse>({
useQuery<TResponse, ApiErrorClass>({
queryKey: [endpoint, queryParam],
queryFn: requesterGetter<TResponse, TQueryParam>(`/${endpoint}`, queryParam?.value),
queryFn: queryFn<TResponse, TQueryParam>(`/${endpoint}`, queryParam?.value),
enabled
});
const useApiWithCursor = <
TApi extends Api<TResponse, TQueryParam>,
TResponse = TApi['response'] & CursorPagination,
TQueryParam = TApi['queryParam']>
(endpoint: string, requesterGetter: ReqesuterGetter) =>
(endpoint: string, queryFn: QueryFunctions) =>
(queryParam?: Ref<TQueryParam | undefined>, enabled?: MaybeRefOrGetter<boolean>) =>
useInfiniteQuery<TResponse & CursorPagination, ApiErrorClass, InfiniteData<TResponse & CursorPagination, Cursor>, QueryKey, Cursor>({
useInfiniteQuery<
TResponse & CursorPagination, ApiErrorClass,
InfiniteData<TResponse & CursorPagination, Cursor>,
QueryKey, Cursor
>({
queryKey: [endpoint, queryParam],
queryFn: requesterGetter<TResponse & CursorPagination, TQueryParam>(`/${endpoint}`, queryParam?.value),
queryFn: queryFn<TResponse & CursorPagination, TQueryParam>(`/${endpoint}`, queryParam?.value),
initialPageParam: '',
getNextPageParam: lastPage => lastPage.pages.nextCursor,
enabled
});

export const useApiForums = () => useApi<ApiForums>('forums', getRequester)();
export const useApiStatus = useApi<ApiStatus>('status', getRequesterWithReCAPTCHA);
export const useApiStatsForumsPostCount = useApi<ApiStatsForumPostCount>('stats/forums/postCount', getRequesterWithReCAPTCHA);
export const useApiUsers = useApi<ApiUsers>('users', getRequesterWithReCAPTCHA);
export const useApiPosts = useApiWithCursor<ApiPosts>('posts', getRequesterWithReCAPTCHA);
export const useApiForums = () => useApi<ApiForums>('forums', queryFunction)();
export const useApiStatus = useApi<ApiStatus>('status', queryFunctionWithReCAPTCHA);
export const useApiStatsForumsPostCount = useApi<ApiStatsForumPostCount>('stats/forums/postCount', queryFunctionWithReCAPTCHA);
export const useApiUsers = useApi<ApiUsers>('users', queryFunctionWithReCAPTCHA);
export const useApiPosts = useApiWithCursor<ApiPosts>('posts', queryFunctionWithReCAPTCHA);
41 changes: 17 additions & 24 deletions fe/src/views/Post.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export type PostRenderer = 'list' | 'table';
const route = useRoute();
const queryParam = ref<ApiPosts['queryParam']>();
const shouldFetch = ref<boolean>(false);
const { data, error, isFetching, isFetchedAfterMount } = useApiPosts(queryParam, shouldFetch);
const { data, error, isFetching, isFetchedAfterMount, dataUpdatedAt, errorUpdatedAt } = useApiPosts(queryParam, shouldFetch);
const selectedRenderTypes = ref<[PostRenderer]>(['list']);
const renderType = computed(() => selectedRenderTypes.value[0]);
const queryFormRef = ref<InstanceType<typeof QueryForm>>();
Expand All @@ -79,8 +79,8 @@ useHead({
})()))
});
const fetchPosts = async (queryParams: ObjUnknown[], isNewQuery: boolean, cursor: Cursor) => {
const startTime = Date.now();
const fetchPosts = (queryParams: ObjUnknown[], isNewQuery: boolean, cursor: Cursor) => {
const startTime = Date.now() / 1000;
queryParam.value = {
query: JSON.stringify(queryParams),
cursor: isNewQuery ? undefined : cursor
Expand All @@ -90,16 +90,14 @@ const fetchPosts = async (queryParams: ObjUnknown[], isNewQuery: boolean, cursor
if (value)
shouldFetch.value = false;
});
if (error.value !== null)
return false;
const networkTime = Date.now() - startTime;
await nextTick(); // wait for child components finish dom update
const postCount = _.sum(Object.values(query.pages.matchQueryPostCount));
const renderTime = ((Date.now() - startTime - networkTime) / 1000).toFixed(2);
notyShow('success', `已加载${postCount}条记录 前端耗时${renderTime}s 后端+网络耗时${networkTime}ms`);
return true;
watchOnce([dataUpdatedAt, errorUpdatedAt], async updatedAt => {
const networkTime = _.max(updatedAt) ?? 0 - startTime;
await nextTick(); // wait for child components finish dom update
const fetchedPage = data.value?.pages.find(i => i.pages.currentCursor === cursor);
const postCount = _.sum(Object.values(fetchedPage?.pages.matchQueryPostCount ?? {}));
const renderTime = (Date.now() / 1000) - startTime - networkTime;
notyShow('success', `已加载${postCount}条记录 前端耗时${renderTime.toFixed(2)}s 后端+网络耗时${networkTime}ms`);
});
};
const scrollToPostListItem = (el: Element) => {
Expand Down Expand Up @@ -144,28 +142,23 @@ watchEffect(() => {
const parseRouteThenFetch = async (newRoute: RouteLocationNormalized, isNewQuery: boolean, cursor: Cursor) => {
if (queryFormRef.value === undefined)
return false;
return;
const flattenParams = await queryFormRef.value.parseRouteToGetFlattenParams(newRoute);
if (flattenParams === false)
return false;
return;
lastFetchingRoute.value = newRoute;
const isFetchSuccess = await fetchPosts(flattenParams, isNewQuery, cursor);
return isFetchSuccess;
fetchPosts(flattenParams, isNewQuery, cursor);
};
onBeforeRouteUpdate(async (to, from) => {
const isNewQuery = useTriggerRouteUpdateStore()
.isTriggeredBy('<QueryForm>@submit', { ...to, force: true })
|| compareRouteIsNewQuery(to, from);
const cursor = getRouteCursorParam(to);
if (!(isNewQuery || _.isEmpty(_.filter(
if (isNewQuery || _.isEmpty(_.filter(
data.value?.pages,
i => i.pages.currentCursor === cursor
))))
return true;
const isFetchSuccess = await parseRouteThenFetch(to, isNewQuery, cursor);
return isNewQuery ? true : isFetchSuccess; // only pass pending route update after successful fetched
)))
await parseRouteThenFetch(to, isNewQuery, cursor);
});
onMounted(async () => {
await parseRouteThenFetch(route, true, getRouteCursorParam(route));
Expand Down
4 changes: 2 additions & 2 deletions fe/src/views/Status.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
import TimeGranularity from '@/components/widgets/TimeGranularity.vue';
import TimeRange from '@/components/widgets/TimeRange.vue';
import { apiStatus, throwIfApiError } from '@/api';
import type { ApiStatus, ApiStatus } from '@/api/index.d';
import { useApiStatus, throwIfApiError } from '@/api';
import type { ApiStatus } from '@/api/index.d';
import { titleTemplate } from '@/shared';
import { commonToolboxFeatures, emptyChartSeriesData } from '@/shared/echarts';
Expand Down

0 comments on commit c7571bb

Please sign in to comment.