From ae2be53f33e43f4f6993dcc74866c9aec3368cf8 Mon Sep 17 00:00:00 2001 From: qschroter Date: Tue, 27 May 2025 16:08:55 +0200 Subject: [PATCH 1/4] add cancellation token mechanism --- src/Bones.UI/core/composableFactory.ts | 15 +++++++++++---- src/Bones.UI/core/serviceFactory.ts | 10 +++++++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Bones.UI/core/composableFactory.ts b/src/Bones.UI/core/composableFactory.ts index b2d9aa8..879632d 100644 --- a/src/Bones.UI/core/composableFactory.ts +++ b/src/Bones.UI/core/composableFactory.ts @@ -9,7 +9,7 @@ export class ComposableFactory { return ComposableFactory.customGet(service, service.get, applyFactory); } - public static getMany(service: { getMany(filter?: TFilter): Promise } & INotifyService, applyFactory?: () => (entities: Ref) => void) { + public static getMany(service: { getMany(filter?: TFilter, controller?: AbortController): Promise } & INotifyService, applyFactory?: () => (entities: Ref) => void) { return ComposableFactory.customGetMany(service, service.getMany, applyFactory); } @@ -115,9 +115,11 @@ export class ComposableFactory { * Warning : read the code before using this method, the first argument in the method is used to create a filter * The last argument passed to the getMany composable can be a custom filter function that will override the default filter * */ - public static customGetMany(service: INotifyService, method: (...args: TArgs) => Promise, applyFactory?: () => (entities: Ref) => void) { + public static customGetMany(service: INotifyService, method: (...args: [...TArgs, AbortController]) => Promise, applyFactory?: () => (entities: Ref) => void) { return () => { const apply = applyFactory ? applyFactory() : () => { }; + + let cancellationToken = new AbortController(); let subscribersIds: number[] = []; onUnmounted(() => { @@ -129,6 +131,11 @@ export class ComposableFactory { const entities = ref([]) as Ref; const getMany = async (...args: [...TArgs, ((el: TDetails) => boolean)?]) => { + + // abort previous request if any + cancellationToken.abort(); + cancellationToken = new AbortController(); + fetching.value = true; let customFilter: ((el: TDetails) => boolean) | undefined = undefined @@ -140,7 +147,7 @@ export class ComposableFactory { let actualArgs = args as unknown as TArgs; try { - entities.value = await method(...actualArgs); + entities.value = await method(...actualArgs, cancellationToken); if (apply) apply(entities) } finally { @@ -153,7 +160,7 @@ export class ComposableFactory { subscribersIds.push(service.subscribe("reset", async () => { fetching.value = true; try { - entities.value = await method(...actualArgs); + entities.value = await method(...actualArgs, cancellationToken); if (apply) apply(entities) } finally { diff --git a/src/Bones.UI/core/serviceFactory.ts b/src/Bones.UI/core/serviceFactory.ts index 3c8c680..87c5500 100644 --- a/src/Bones.UI/core/serviceFactory.ts +++ b/src/Bones.UI/core/serviceFactory.ts @@ -38,7 +38,7 @@ export class ServiceFactory { addGetMany(url: string | (() => string), entity: new (dto: TInfosDTO) => TInfos) : { getMany: (filter?: TFilter) => Promise } { - const getMany = async (filter?: TFilter) => { + const getMany = async (filter?: TFilter, controller?: AbortController) => { const realUrl = typeof url === "string" ? url : url(); let response; @@ -46,10 +46,14 @@ export class ServiceFactory { // If the service is configured to use GET as POST to prevent issues with large query strings, // we send the filter as a POST request with a "_method" parameter to indicate it's a GET request. if (ServiceFactory.getAsPost && filter) { - response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter); + response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter, { + signal: controller?.signal + }); } else { - response = await ServiceFactory.http.get(buildURL(realUrl, filter)); + response = await ServiceFactory.http.get(buildURL(realUrl, filter), { + signal: controller?.signal + }); } const dtos: TInfosDTO[] = response.data; From fbb3962d5d2815b009952036bfa2c52fc72b33a7 Mon Sep 17 00:00:00 2001 From: qschroter Date: Tue, 27 May 2025 20:05:35 +0200 Subject: [PATCH 2/4] works and good compromise --- src/Bones.UI/core/serviceFactory.ts | 19 +++++++++++++++++-- .../services/testUserService.ts | 4 ++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Bones.UI/core/serviceFactory.ts b/src/Bones.UI/core/serviceFactory.ts index 87c5500..c298ac1 100644 --- a/src/Bones.UI/core/serviceFactory.ts +++ b/src/Bones.UI/core/serviceFactory.ts @@ -36,7 +36,7 @@ export class ServiceFactory { } addGetMany(url: string | (() => string), entity: new (dto: TInfosDTO) => TInfos) - : { getMany: (filter?: TFilter) => Promise } { + : { getMany: (filter?: TFilter, controller?: AbortController) => Promise } { const getMany = async (filter?: TFilter, controller?: AbortController) => { const realUrl = typeof url === "string" ? url : url(); @@ -80,7 +80,7 @@ export class ServiceFactory { return { get }; } - static addCustom(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise, mapper: (dto: TResultDTO) => TResult): Record Promise> { + static addCustom(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise, mapper: (dto: TResultDTO) => TResult) { const fetch = async (...args: TArgs) => { const response = await call(ServiceFactory.http, ...args); @@ -94,6 +94,21 @@ export class ServiceFactory { return { [name]: fetch } as Record Promise>; } + + static addCancellable(name: T, call: (axios: AxiosInstance, ...args: [...TArgs, AbortController]) => Promise, mapper: (dto: TResultDTO) => TResult) { + + const fetch = async (...args: [...TArgs, AbortController]) => { + const response = await call(ServiceFactory.http, ...args); + const dto: TResultDTO = response.data; + + const result = mapper(dto); + + return result; + } + + return { [name]: fetch } as Record Promise>; + } + addCreate(url: string | (() => string)) : { create: (dto: TCreateDTO) => Promise } { diff --git a/tests/Bones.UI.Tests/services/testUserService.ts b/tests/Bones.UI.Tests/services/testUserService.ts index 2bb3b96..d020d4f 100644 --- a/tests/Bones.UI.Tests/services/testUserService.ts +++ b/tests/Bones.UI.Tests/services/testUserService.ts @@ -13,11 +13,11 @@ const AccountLoginFactory = new ServiceFactory ({ reset: () => notify.notify("reset"), })), - ServiceFactory.addCustom("login", (axios, d: CreateTestUserDTO) => axios.post(TEST_USERS_URL, d), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), + ServiceFactory.addCustom("login", (axios, d: CreateTestUserDTO, controller: AbortController) => axios.post(TEST_USERS_URL, d), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), ServiceFactory.addCustom("logout", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("current", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("complexCurrent", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), - ServiceFactory.addCustom("complexGetMany", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), + ServiceFactory.addCancellable("complexGetMany", (axios, p1: string, p2: number, controller: AbortController) => axios.get(TEST_USERS_URL, { signal: controller.signal }), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), )); export const useTestUsersSync = ComposableFactory.sync(testUserServiceFactory); From 179972f4626fa769d9b612fbb2aba89656ce9c37 Mon Sep 17 00:00:00 2001 From: qschroter Date: Tue, 27 May 2025 20:06:11 +0200 Subject: [PATCH 3/4] fix typo --- tests/Bones.UI.Tests/services/testUserService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Bones.UI.Tests/services/testUserService.ts b/tests/Bones.UI.Tests/services/testUserService.ts index d020d4f..b87aeeb 100644 --- a/tests/Bones.UI.Tests/services/testUserService.ts +++ b/tests/Bones.UI.Tests/services/testUserService.ts @@ -13,7 +13,7 @@ const AccountLoginFactory = new ServiceFactory ({ reset: () => notify.notify("reset"), })), - ServiceFactory.addCustom("login", (axios, d: CreateTestUserDTO, controller: AbortController) => axios.post(TEST_USERS_URL, d), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), + ServiceFactory.addCustom("login", (axios, d: CreateTestUserDTO) => axios.post(TEST_USERS_URL, d), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), ServiceFactory.addCustom("logout", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("current", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("complexCurrent", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), From ab18d66b6bffa987d7515302c9ee5fad4cc5e925 Mon Sep 17 00:00:00 2001 From: qschroter Date: Wed, 28 May 2025 12:31:59 +0200 Subject: [PATCH 4/4] rename ServiceFactory.custom --- src/Bones.UI/core/serviceFactory.ts | 2 +- tests/Bones.UI.Tests/services/testUserService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bones.UI/core/serviceFactory.ts b/src/Bones.UI/core/serviceFactory.ts index c298ac1..7993231 100644 --- a/src/Bones.UI/core/serviceFactory.ts +++ b/src/Bones.UI/core/serviceFactory.ts @@ -95,7 +95,7 @@ export class ServiceFactory { } - static addCancellable(name: T, call: (axios: AxiosInstance, ...args: [...TArgs, AbortController]) => Promise, mapper: (dto: TResultDTO) => TResult) { + static addCustomCancellable(name: T, call: (axios: AxiosInstance, ...args: [...TArgs, AbortController]) => Promise, mapper: (dto: TResultDTO) => TResult) { const fetch = async (...args: [...TArgs, AbortController]) => { const response = await call(ServiceFactory.http, ...args); diff --git a/tests/Bones.UI.Tests/services/testUserService.ts b/tests/Bones.UI.Tests/services/testUserService.ts index b87aeeb..c1c3cec 100644 --- a/tests/Bones.UI.Tests/services/testUserService.ts +++ b/tests/Bones.UI.Tests/services/testUserService.ts @@ -17,7 +17,7 @@ const AccountLoginFactory = new ServiceFactory axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("current", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("complexCurrent", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), - ServiceFactory.addCancellable("complexGetMany", (axios, p1: string, p2: number, controller: AbortController) => axios.get(TEST_USERS_URL, { signal: controller.signal }), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), + ServiceFactory.addCustomCancellable("complexGetMany", (axios, p1: string, p2: number, controller: AbortController) => axios.get(TEST_USERS_URL, { signal: controller.signal }), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), )); export const useTestUsersSync = ComposableFactory.sync(testUserServiceFactory);