From 64f3e5fa39d9fab77a266c7fa2dc16870ad36cd3 Mon Sep 17 00:00:00 2001 From: wangcch Date: Tue, 16 Jul 2024 16:57:01 +0800 Subject: [PATCH 1/4] feat(useResource): asyncReq option Control the return value of the request --- src/request.ts | 4 ++++ src/useResource.ts | 49 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/request.ts b/src/request.ts index b2709d1..bad179d 100644 --- a/src/request.ts +++ b/src/request.ts @@ -51,6 +51,10 @@ export type RequestDispatcher = ( ...args: Parameters ) => Canceler; +export type RequestAsyncFunc = ( + ...args: Parameters +) => Promise, Payload]>; + /** * Normalize the error response returned from `@axios-use/vue` */ diff --git a/src/useResource.ts b/src/useResource.ts index 84560fa..ece032e 100644 --- a/src/useResource.ts +++ b/src/useResource.ts @@ -8,6 +8,7 @@ import type { BodyData, RequestError, Request, + RequestAsyncFunc, RequestDispatcher, RequestCallbackFn, } from "./request"; @@ -29,25 +30,27 @@ export type UseResourceResultState = ComputedRef< RequestState >; -export type UseResourceResult = [ +export type UseResourceResult = [ /** Response data group */ UseResourceResultState, /** A function that enables you to re-execute the request. And pass in new variables */ - RequestDispatcher, + A extends true ? RequestAsyncFunc : RequestDispatcher, /** A function that enables you to re-execute the request. Keep the latest variables */ () => Canceler | undefined, /** A function that cancel the request */ Canceler, ]; -export type UseResourceOptions = Pick< - RequestConfigType, - "instance" | "getResponseItem" -> & +export type UseResourceOptions< + T extends Request, + A extends boolean = false, +> = Pick & RequestCallbackFn & { /** Conditional Fetching */ filter?: (...args: Parameters) => boolean; defaultState?: RequestState; + /** Control the return value of the request */ + asyncReq?: A; }; function getDefaultStateLoading( @@ -87,11 +90,11 @@ export type RequestDepsParameters = | FullRefArrayItem> | ComputedRef>; -export function useResource( +export function useResource( fn: T, requestParams?: RequestDepsParameters | false, - options?: UseResourceOptions, -): UseResourceResult { + options?: UseResourceOptions, +): UseResourceResult { const [createRequest, { clear }] = useRequest(fn, { onCompleted: options?.onCompleted, onError: options?.onError, @@ -107,7 +110,7 @@ export function useResource( ...options?.defaultState, }); - const request = (...args: Parameters) => { + const _reqDispatcher = (...args: Parameters) => { clear(REQUEST_CLEAR_MESSAGE); const { ready, cancel } = createRequest(...args); @@ -126,6 +129,26 @@ export function useResource( return cancel; }; + const _reqAsync = async (...args: Parameters) => { + clear(REQUEST_CLEAR_MESSAGE); + + const { ready } = createRequest(...args); + + try { + dispatch({ type: "start" }); + const [data, response] = await ready(); + dispatch({ type: "success", data, response }); + return [data, response]; + } catch (e) { + const error = e as RequestError, BodyData>; + if (!error.isCancel) { + dispatch({ type: "error", error }); + } + throw e; + } + }; + const request = (...args: Parameters) => + options?.asyncReq ? _reqAsync(...args) : _reqDispatcher(...args); const refresh = () => { const _args = unrefs(requestParams || []) as Parameters; @@ -133,7 +156,7 @@ export function useResource( typeof options?.filter === "function" ? options.filter(..._args) : true; if (_filter) { - return request(..._args); + return _reqDispatcher(..._args); } return undefined; @@ -160,7 +183,7 @@ export function useResource( }, ); - const _rtnState = computed>(() => unref(state)); + const rtnState = computed>(() => unref(state)); - return [_rtnState, request, refresh, cancel]; + return [rtnState, request, refresh, cancel] as UseResourceResult; } From c8b5375aa88702e55916196b576a9bcc3c90e3b7 Mon Sep 17 00:00:00 2001 From: wangcch Date: Tue, 16 Jul 2024 16:58:12 +0800 Subject: [PATCH 2/4] test(useResource): asyncReq then and catch --- tests/useResource.test.ts | 91 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/tests/useResource.test.ts b/tests/useResource.test.ts index 9c48e45..7cfbef6 100644 --- a/tests/useResource.test.ts +++ b/tests/useResource.test.ts @@ -1,8 +1,9 @@ import { describe, expect, test, expectTypeOf } from "vitest"; import { mount, flushPromises } from "@vue/test-utils"; -import type { AxiosResponse } from "axios"; +import type { AxiosError, AxiosResponse } from "axios"; import { computed, defineComponent, h, ref, unref, reactive } from "vue"; +import type { RequestError } from "../src"; import { useResource, _request } from "../src"; import type { MockDataUserItem } from "./setup/mock-request"; import { @@ -348,4 +349,92 @@ describe("useResource", () => { mount(Component); }); + + test("async request function", async () => { + const Component = defineComponent({ + setup() { + const params = reactive({ id: "1" }); + const [res, req] = useResource(getAPIFuncs(true).user.get, [params], { + filter: (p) => Boolean(p?.id), + asyncReq: true, + }); + + expect(unref(res).isLoading).toBeTruthy(); + expect(unref(res).data).toBeUndefined(); + expect(unref(res).response).toBeUndefined(); + expect(unref(res).error).toBeUndefined(); + + const handleChangeId = () => { + req({ id: "2" }).then(([d, r]) => { + expectTypeOf(d).toEqualTypeOf(); + expectTypeOf(r).toEqualTypeOf>(); + + const _cur = MOCK_DATA_USER_LIST.find((i) => i.id === "2"); + expect(d).toStrictEqual(_cur); + expect(r.data).toStrictEqual(_cur); + }); + }; + const handleReqErr = () => { + req({ id: "100" }) + .then(() => { + expect(1).toBe(2); + }) + .catch((e) => { + const _e = e as RequestError; + expect(_e.original.response?.status).toBe(404); + }); + }; + + return () => + h("div", [ + h("button", { + "data-t-id": "change_id", + onClick: handleChangeId, + }), + h("button", { + "data-t-id": "req_err", + onClick: handleReqErr, + }), + h( + "div", + { "data-t-id": "res.data" }, + JSON.stringify(res.value.data), + ), + h( + "div", + { "data-t-id": "res.err" }, + JSON.stringify(res.value.error), + ), + h("div", { "data-t-id": "res.isLoading" }, res.value.isLoading), + h("div", { "data-t-id": "params.id" }, params.id), + ]); + }, + }); + + const wrapper = mount(Component); + await flushPromises(); + expect(wrapper.get('[data-t-id="res.data"]').text()).toBe( + JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "1")), + ); + expect(wrapper.get('[data-t-id="res.err"]').text()).toBe(""); + expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false"); + expect(wrapper.get('[data-t-id="params.id"]').text()).toBe("1"); + + wrapper.get('[data-t-id="change_id"]').trigger("click"); + await flushPromises(); + expect(wrapper.get('[data-t-id="res.data"]').text()).toBe( + JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "2")), + ); + expect(wrapper.get('[data-t-id="res.err"]').text()).toBe(""); + expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false"); + expect(wrapper.get('[data-t-id="params.id"]').text()).toBe("1"); + + wrapper.get('[data-t-id="req_err"]').trigger("click"); + await flushPromises(); + expect(wrapper.get('[data-t-id="res.isLoading"]').text()).toBe("false"); + expect(wrapper.get('[data-t-id="res.data"]').text()).toBe( + JSON.stringify(MOCK_DATA_USER_LIST.find((i) => i.id === "2")), + ); + expect(wrapper.get('[data-t-id="res.err"]').text()).not.toBe(""); + }); }); From d398fc08238616ae853008ae495ba0dfc8ab7aa1 Mon Sep 17 00:00:00 2001 From: wangcch Date: Wed, 17 Jul 2024 16:21:20 +0800 Subject: [PATCH 3/4] test(context): empty options(setUseRequestConfig) --- tests/context.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/context.test.ts b/tests/context.test.ts index 14960d2..25febd6 100644 --- a/tests/context.test.ts +++ b/tests/context.test.ts @@ -67,6 +67,21 @@ describe("context", () => { }); }); + test("setUseRequestConfig empty options", () => { + const Component = defineComponent({ + setup() { + const { instance } = getUseRequestConfig(); + expect(instance).toBe(axios); + + return () => h("div"); + }, + }); + + mount(Component, (app) => { + setUseRequestConfig(app); + }); + }); + test("setUseRequestConfig (vue 2)", () => { const Component = defineComponent2({ setup() { From c34dac3b6f588290302a56646814d7a0b21b2d2b Mon Sep 17 00:00:00 2001 From: wangcch Date: Fri, 26 Jul 2024 14:08:11 +0800 Subject: [PATCH 4/4] docs(useResource): options.asyncReq --- README.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c3aaec..b812980 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ const [createRequest, { hasPending, cancel }] = useRequest( | options.onError | function | This function is passed an `RequestError` object | | options.instance | `AxiosInstance` | Customize the Axios instance of the current item | | options.getResponseItem | function | custom returns the value of `data`(index 0). | +| options.asyncReq | boolean | Control the return value of the request | ```ts // js @@ -184,6 +185,8 @@ type ReqState = ComputedRef<{ // `options.filter` will not be called type Fetch = (...args: Parameters) => Canceler; +// if `options.asyncReq` is `true` +type Fetch = (...args: Parameters) => Promise<[Payload, AxiosResponse]>; // 1. Same as `fetch`. But no parameters required. Inherit `useResource` parameters // 2. Will call `options.filter` diff --git a/package.json b/package.json index 5ae5fa6..f3fcdb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@axios-use/vue", - "version": "0.2.5", + "version": "0.3.0-alpha.1", "description": "A Vue composition utilities for Axios.", "type": "module", "main": "lib/index.cjs",