@yh-ui/request 1.0.52 → 1.0.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,6 +49,9 @@ class HttpCache {
49
49
  if (!entry) return void 0;
50
50
  const now = Date.now();
51
51
  if (entry.expireTime && now > entry.expireTime) {
52
+ if (this.options.staleWhileRevalidate && now <= entry.expireTime + this.options.staleTime) {
53
+ return entry;
54
+ }
52
55
  this.cache.delete(key);
53
56
  return void 0;
54
57
  }
@@ -193,6 +196,7 @@ function createHttpCacheInterceptor(options = {}) {
193
196
  const key = `${response.config.method}:${response.config.fullPath || response.config.url}`;
194
197
  const entry = cache.get(key);
195
198
  if (entry) {
199
+ cache.set(key, entry.data, response.response);
196
200
  return {
197
201
  ...response,
198
202
  data: entry.data
@@ -35,6 +35,9 @@ export class HttpCache {
35
35
  if (!entry) return void 0;
36
36
  const now = Date.now();
37
37
  if (entry.expireTime && now > entry.expireTime) {
38
+ if (this.options.staleWhileRevalidate && now <= entry.expireTime + this.options.staleTime) {
39
+ return entry;
40
+ }
38
41
  this.cache.delete(key);
39
42
  return void 0;
40
43
  }
@@ -175,6 +178,7 @@ export function createHttpCacheInterceptor(options = {}) {
175
178
  const key = `${response.config.method}:${response.config.fullPath || response.config.url}`;
176
179
  const entry = cache.get(key);
177
180
  if (entry) {
181
+ cache.set(key, entry.data, response.response);
178
182
  return {
179
183
  ...response,
180
184
  data: entry.data
package/dist/request.cjs CHANGED
@@ -71,8 +71,14 @@ function createRequestError(error, config, response) {
71
71
  requestError.isNetworkError = true;
72
72
  requestError.code = "NETWORK_ERROR";
73
73
  } else if (config?.signal?.aborted) {
74
- requestError.isCanceled = true;
75
- requestError.code = "CANCELED";
74
+ const isTimeout = err.message.toLowerCase().includes("timeout") || err.name === "TimeoutError" || (config.signal.reason instanceof Error ? config.signal.reason.message.toLowerCase().includes("timeout") : String(config.signal.reason).toLowerCase().includes("timeout"));
75
+ if (isTimeout) {
76
+ requestError.isTimeout = true;
77
+ requestError.code = "TIMEOUT";
78
+ } else {
79
+ requestError.isCanceled = true;
80
+ requestError.code = "CANCELED";
81
+ }
76
82
  } else if (response) {
77
83
  requestError.code = `HTTP_${response.status}`;
78
84
  }
package/dist/request.mjs CHANGED
@@ -49,8 +49,14 @@ export function createRequestError(error, config, response) {
49
49
  requestError.isNetworkError = true;
50
50
  requestError.code = "NETWORK_ERROR";
51
51
  } else if (config?.signal?.aborted) {
52
- requestError.isCanceled = true;
53
- requestError.code = "CANCELED";
52
+ const isTimeout = err.message.toLowerCase().includes("timeout") || err.name === "TimeoutError" || (config.signal.reason instanceof Error ? config.signal.reason.message.toLowerCase().includes("timeout") : String(config.signal.reason).toLowerCase().includes("timeout"));
53
+ if (isTimeout) {
54
+ requestError.isTimeout = true;
55
+ requestError.code = "TIMEOUT";
56
+ } else {
57
+ requestError.isCanceled = true;
58
+ requestError.code = "CANCELED";
59
+ }
54
60
  } else if (response) {
55
61
  requestError.code = `HTTP_${response.status}`;
56
62
  }
@@ -40,6 +40,7 @@ function useLoadMore(service, options = {}) {
40
40
  const loadService = loadMoreService || service;
41
41
  const loadData = async (isRefresh = false) => {
42
42
  if (loading.value || loadingMore.value) return;
43
+ const requestedPage = current.value;
43
44
  const isLoadMoreOp = !isRefresh;
44
45
  loading.value = isLoadMoreOp ? false : true;
45
46
  if (isRefresh) refreshing.value = true;
@@ -55,7 +56,7 @@ function useLoadMore(service, options = {}) {
55
56
  }
56
57
  if (isRefresh) {
57
58
  data.value = pageData;
58
- current.value = initialPage + 1;
59
+ current.value = requestedPage + 1;
59
60
  } else {
60
61
  const oldData = data.value;
61
62
  if (Array.isArray(oldData) && Array.isArray(pageData)) {
@@ -63,7 +64,7 @@ function useLoadMore(service, options = {}) {
63
64
  } else {
64
65
  data.value = pageData;
65
66
  }
66
- current.value++;
67
+ current.value = requestedPage + 1;
67
68
  }
68
69
  onSuccess?.(pageData, params.value);
69
70
  } catch (err) {
@@ -34,6 +34,7 @@ export function useLoadMore(service, options = {}) {
34
34
  const loadService = loadMoreService || service;
35
35
  const loadData = async (isRefresh = false) => {
36
36
  if (loading.value || loadingMore.value) return;
37
+ const requestedPage = current.value;
37
38
  const isLoadMoreOp = !isRefresh;
38
39
  loading.value = isLoadMoreOp ? false : true;
39
40
  if (isRefresh) refreshing.value = true;
@@ -53,7 +54,7 @@ export function useLoadMore(service, options = {}) {
53
54
  }
54
55
  if (isRefresh) {
55
56
  data.value = pageData;
56
- current.value = initialPage + 1;
57
+ current.value = requestedPage + 1;
57
58
  } else {
58
59
  const oldData = data.value;
59
60
  if (Array.isArray(oldData) && Array.isArray(pageData)) {
@@ -61,7 +62,7 @@ export function useLoadMore(service, options = {}) {
61
62
  } else {
62
63
  data.value = pageData;
63
64
  }
64
- current.value++;
65
+ current.value = requestedPage + 1;
65
66
  }
66
67
  onSuccess?.(pageData, params.value);
67
68
  } catch (err) {
@@ -69,8 +69,16 @@ function usePagination(service, options = {}) {
69
69
  };
70
70
  const loadPage = async page => {
71
71
  if (page < 1 || totalPages.value > 0 && page > totalPages.value) return;
72
+ if (current.value === page) {
73
+ if (manual) {
74
+ await loadData();
75
+ }
76
+ return;
77
+ }
72
78
  current.value = page;
73
- await loadData();
79
+ if (manual) {
80
+ await loadData();
81
+ }
74
82
  };
75
83
  const nextPage = async () => {
76
84
  if (noMore.value) return;
@@ -86,14 +94,31 @@ function usePagination(service, options = {}) {
86
94
  await loadPage(totalPages.value);
87
95
  };
88
96
  const refresh = async () => {
89
- current.value = 1;
90
97
  refreshing.value = true;
91
- await loadData();
98
+ if (current.value === 1) {
99
+ await loadData();
100
+ } else {
101
+ current.value = 1;
102
+ if (manual) {
103
+ await loadData();
104
+ }
105
+ }
92
106
  };
93
107
  const setPageSize = size => {
108
+ if (pageSize.value === size) {
109
+ if (manual) {
110
+ loadData();
111
+ }
112
+ return;
113
+ }
94
114
  pageSize.value = size;
95
- if (!manual) {
96
- refresh();
115
+ if (manual) {
116
+ if (current.value === 1) {
117
+ loadData();
118
+ } else {
119
+ current.value = 1;
120
+ loadData();
121
+ }
97
122
  }
98
123
  };
99
124
  const setTotal = newTotal => {
@@ -101,8 +126,11 @@ function usePagination(service, options = {}) {
101
126
  };
102
127
  (0, _vue.watch)(pageSize, () => {
103
128
  if (!manual) {
104
- current.value = 1;
105
- loadData();
129
+ if (current.value === 1) {
130
+ loadData();
131
+ } else {
132
+ current.value = 1;
133
+ }
106
134
  }
107
135
  });
108
136
  (0, _vue.watch)(current, () => {
@@ -60,8 +60,16 @@ export function usePagination(service, options = {}) {
60
60
  };
61
61
  const loadPage = async (page) => {
62
62
  if (page < 1 || totalPages.value > 0 && page > totalPages.value) return;
63
+ if (current.value === page) {
64
+ if (manual) {
65
+ await loadData();
66
+ }
67
+ return;
68
+ }
63
69
  current.value = page;
64
- await loadData();
70
+ if (manual) {
71
+ await loadData();
72
+ }
65
73
  };
66
74
  const nextPage = async () => {
67
75
  if (noMore.value) return;
@@ -77,14 +85,31 @@ export function usePagination(service, options = {}) {
77
85
  await loadPage(totalPages.value);
78
86
  };
79
87
  const refresh = async () => {
80
- current.value = 1;
81
88
  refreshing.value = true;
82
- await loadData();
89
+ if (current.value === 1) {
90
+ await loadData();
91
+ } else {
92
+ current.value = 1;
93
+ if (manual) {
94
+ await loadData();
95
+ }
96
+ }
83
97
  };
84
98
  const setPageSize = (size) => {
99
+ if (pageSize.value === size) {
100
+ if (manual) {
101
+ loadData();
102
+ }
103
+ return;
104
+ }
85
105
  pageSize.value = size;
86
- if (!manual) {
87
- refresh();
106
+ if (manual) {
107
+ if (current.value === 1) {
108
+ loadData();
109
+ } else {
110
+ current.value = 1;
111
+ loadData();
112
+ }
88
113
  }
89
114
  };
90
115
  const setTotal = (newTotal) => {
@@ -92,8 +117,11 @@ export function usePagination(service, options = {}) {
92
117
  };
93
118
  watch(pageSize, () => {
94
119
  if (!manual) {
95
- current.value = 1;
96
- loadData();
120
+ if (current.value === 1) {
121
+ loadData();
122
+ } else {
123
+ current.value = 1;
124
+ }
97
125
  }
98
126
  });
99
127
  watch(current, () => {
package/dist/useQueue.cjs CHANGED
@@ -20,6 +20,7 @@ function useQueue(options = {}) {
20
20
  } = options;
21
21
  const taskList = (0, _vue.reactive)([]);
22
22
  const taskMap = /* @__PURE__ */new Map();
23
+ const abortControllers = /* @__PURE__ */new Map();
23
24
  const isRunning = (0, _vue.ref)(false);
24
25
  const isPaused = (0, _vue.ref)(false);
25
26
  const currentTaskIds = /* @__PURE__ */new Set();
@@ -44,23 +45,34 @@ function useQueue(options = {}) {
44
45
  const totalCount = (0, _vue.computed)(() => tasks.value.length);
45
46
  const executeTask = async task => {
46
47
  if (task.status === "canceled" || task.status === "running") return;
48
+ const controller = new AbortController();
49
+ abortControllers.set(task.id, controller);
47
50
  task.status = "running";
48
51
  task.startTime = Date.now();
49
52
  currentTaskIds.add(task.id);
50
53
  triggerUpdate();
51
54
  try {
52
55
  if (task.delay && task.delay > 0) {
53
- await new Promise(resolve => setTimeout(resolve, task.delay));
56
+ await new Promise((resolve, reject) => {
57
+ const timeoutId = setTimeout(resolve, task.delay);
58
+ controller.signal.addEventListener("abort", () => {
59
+ clearTimeout(timeoutId);
60
+ reject(new Error("Canceled"));
61
+ });
62
+ });
54
63
  }
55
- if (task.status === "canceled") return;
56
- const result = await task.task();
57
- if (task.status === "canceled") return;
64
+ if (task.status === "canceled" || controller.signal.aborted) return;
65
+ const result = await task.task({
66
+ signal: controller.signal
67
+ });
68
+ if (task.status === "canceled" || controller.signal.aborted) return;
58
69
  task.status = "fulfilled";
59
70
  task.result = result;
60
71
  task.endTime = Date.now();
61
72
  triggerUpdate();
62
73
  onTaskComplete?.(task);
63
74
  } catch (error) {
75
+ if (task.status === "canceled" || controller.signal.aborted) return;
64
76
  task.status = "rejected";
65
77
  task.error = error instanceof Error ? error : new Error(String(error));
66
78
  task.endTime = Date.now();
@@ -71,6 +83,7 @@ function useQueue(options = {}) {
71
83
  }
72
84
  } finally {
73
85
  currentTaskIds.delete(task.id);
86
+ abortControllers.delete(task.id);
74
87
  }
75
88
  };
76
89
  const processQueue = () => {
@@ -131,6 +144,10 @@ function useQueue(options = {}) {
131
144
  taskMap.forEach(task => {
132
145
  if (task.status === "pending" || task.status === "running") {
133
146
  task.status = "canceled";
147
+ const controller = abortControllers.get(task.id);
148
+ if (controller) {
149
+ controller.abort();
150
+ }
134
151
  }
135
152
  });
136
153
  taskMap.clear();
@@ -159,22 +176,28 @@ function useQueue(options = {}) {
159
176
  const cancel = taskId => {
160
177
  const task = taskMap.get(taskId);
161
178
  if (task) {
162
- if (task.status === "pending") {
163
- task.status = "canceled";
179
+ const prevStatus = task.status;
180
+ task.status = "canceled";
181
+ const controller = abortControllers.get(taskId);
182
+ if (controller) {
183
+ controller.abort();
184
+ }
185
+ if (prevStatus === "pending") {
164
186
  taskMap.delete(taskId);
165
187
  const index = taskList.findIndex(t => t.id === taskId);
166
188
  if (index > -1) taskList.splice(index, 1);
167
- triggerUpdate();
168
- } else if (task.status === "running") {
169
- task.status = "canceled";
170
- triggerUpdate();
171
189
  }
190
+ triggerUpdate();
172
191
  }
173
192
  };
174
193
  const cancelAll = () => {
175
194
  taskMap.forEach(task => {
176
195
  if (task.status === "pending" || task.status === "running") {
177
196
  task.status = "canceled";
197
+ const controller = abortControllers.get(task.id);
198
+ if (controller) {
199
+ controller.abort();
200
+ }
178
201
  }
179
202
  });
180
203
  currentTaskIds.clear();
@@ -8,7 +8,9 @@ export interface QueueTask<T = unknown> {
8
8
  /** 任务 key(用于去重) */
9
9
  key?: string;
10
10
  /** 任务函数 */
11
- task: () => Promise<T>;
11
+ task: (options: {
12
+ signal: AbortSignal;
13
+ }) => Promise<T>;
12
14
  /** 任务优先级(数字越大优先级越高) */
13
15
  priority?: number;
14
16
  /** 任务状态 */
@@ -73,7 +75,9 @@ export interface UseQueueReturn<T = unknown> {
73
75
  /** 总数量 */
74
76
  totalCount: ComputedRef<number>;
75
77
  /** 添加任务 */
76
- add: <R = unknown>(task: () => Promise<R>, options?: AddTaskOptions) => string;
78
+ add: <R = unknown>(task: (options: {
79
+ signal: AbortSignal;
80
+ }) => Promise<R>, options?: AddTaskOptions) => string;
77
81
  /** 移除任务 */
78
82
  remove: (taskId: string) => void;
79
83
  /** 清空队列 */
package/dist/useQueue.mjs CHANGED
@@ -14,6 +14,7 @@ export function useQueue(options = {}) {
14
14
  } = options;
15
15
  const taskList = reactive([]);
16
16
  const taskMap = /* @__PURE__ */ new Map();
17
+ const abortControllers = /* @__PURE__ */ new Map();
17
18
  const isRunning = ref(false);
18
19
  const isPaused = ref(false);
19
20
  const currentTaskIds = /* @__PURE__ */ new Set();
@@ -40,23 +41,32 @@ export function useQueue(options = {}) {
40
41
  const totalCount = computed(() => tasks.value.length);
41
42
  const executeTask = async (task) => {
42
43
  if (task.status === "canceled" || task.status === "running") return;
44
+ const controller = new AbortController();
45
+ abortControllers.set(task.id, controller);
43
46
  task.status = "running";
44
47
  task.startTime = Date.now();
45
48
  currentTaskIds.add(task.id);
46
49
  triggerUpdate();
47
50
  try {
48
51
  if (task.delay && task.delay > 0) {
49
- await new Promise((resolve) => setTimeout(resolve, task.delay));
52
+ await new Promise((resolve, reject) => {
53
+ const timeoutId = setTimeout(resolve, task.delay);
54
+ controller.signal.addEventListener("abort", () => {
55
+ clearTimeout(timeoutId);
56
+ reject(new Error("Canceled"));
57
+ });
58
+ });
50
59
  }
51
- if (task.status === "canceled") return;
52
- const result = await task.task();
53
- if (task.status === "canceled") return;
60
+ if (task.status === "canceled" || controller.signal.aborted) return;
61
+ const result = await task.task({ signal: controller.signal });
62
+ if (task.status === "canceled" || controller.signal.aborted) return;
54
63
  task.status = "fulfilled";
55
64
  task.result = result;
56
65
  task.endTime = Date.now();
57
66
  triggerUpdate();
58
67
  onTaskComplete?.(task);
59
68
  } catch (error) {
69
+ if (task.status === "canceled" || controller.signal.aborted) return;
60
70
  task.status = "rejected";
61
71
  task.error = error instanceof Error ? error : new Error(String(error));
62
72
  task.endTime = Date.now();
@@ -67,6 +77,7 @@ export function useQueue(options = {}) {
67
77
  }
68
78
  } finally {
69
79
  currentTaskIds.delete(task.id);
80
+ abortControllers.delete(task.id);
70
81
  }
71
82
  };
72
83
  const processQueue = () => {
@@ -127,6 +138,10 @@ export function useQueue(options = {}) {
127
138
  taskMap.forEach((task) => {
128
139
  if (task.status === "pending" || task.status === "running") {
129
140
  task.status = "canceled";
141
+ const controller = abortControllers.get(task.id);
142
+ if (controller) {
143
+ controller.abort();
144
+ }
130
145
  }
131
146
  });
132
147
  taskMap.clear();
@@ -155,22 +170,28 @@ export function useQueue(options = {}) {
155
170
  const cancel = (taskId) => {
156
171
  const task = taskMap.get(taskId);
157
172
  if (task) {
158
- if (task.status === "pending") {
159
- task.status = "canceled";
173
+ const prevStatus = task.status;
174
+ task.status = "canceled";
175
+ const controller = abortControllers.get(taskId);
176
+ if (controller) {
177
+ controller.abort();
178
+ }
179
+ if (prevStatus === "pending") {
160
180
  taskMap.delete(taskId);
161
181
  const index = taskList.findIndex((t) => t.id === taskId);
162
182
  if (index > -1) taskList.splice(index, 1);
163
- triggerUpdate();
164
- } else if (task.status === "running") {
165
- task.status = "canceled";
166
- triggerUpdate();
167
183
  }
184
+ triggerUpdate();
168
185
  }
169
186
  };
170
187
  const cancelAll = () => {
171
188
  taskMap.forEach((task) => {
172
189
  if (task.status === "pending" || task.status === "running") {
173
190
  task.status = "canceled";
191
+ const controller = abortControllers.get(task.id);
192
+ if (controller) {
193
+ controller.abort();
194
+ }
174
195
  }
175
196
  });
176
197
  currentTaskIds.clear();
@@ -16,6 +16,16 @@ function setCache(key, value, cacheTime) {
16
16
  expireTime
17
17
  });
18
18
  }
19
+ function getCache(key) {
20
+ const cached = globalCache.get(key);
21
+ if (cached) {
22
+ if (Date.now() < cached.expireTime) {
23
+ return cached.data;
24
+ }
25
+ globalCache.delete(key);
26
+ }
27
+ return void 0;
28
+ }
19
29
  function useRequest(service, options = {}) {
20
30
  const {
21
31
  manual = false,
@@ -55,7 +65,9 @@ function useRequest(service, options = {}) {
55
65
  loading.value = true;
56
66
  error.value = void 0;
57
67
  try {
58
- const response = await service(...runParams);
68
+ const response = await service(...runParams, {
69
+ signal: abortController.signal
70
+ });
59
71
  if (currentRunCount !== runCount.value) {
60
72
  const cancelError = new Error("Request canceled");
61
73
  cancelError.isCanceled = true;
@@ -95,10 +107,12 @@ function useRequest(service, options = {}) {
95
107
  }
96
108
  };
97
109
  const cancel = () => {
110
+ runCount.value++;
98
111
  if (abortController) {
99
112
  abortController.abort();
100
113
  abortController = null;
101
114
  }
115
+ cancelFn?.();
102
116
  loading.value = false;
103
117
  };
104
118
  const refresh = async () => {
@@ -166,6 +180,7 @@ function useRequestSWR(cacheKey, service, options = {}) {
166
180
  const {
167
181
  cacheTime = 10 * 60 * 1e3,
168
182
  setCache: customSetCache,
183
+ getCache: customGetCache,
169
184
  refreshOnWindowFocus = false,
170
185
  refreshDepsWait = 1e3,
171
186
  refreshDeps = [],
@@ -173,6 +188,7 @@ function useRequestSWR(cacheKey, service, options = {}) {
173
188
  ...requestOptions
174
189
  } = options;
175
190
  const setCacheFn = customSetCache || ((key, value) => setCache(key, value, cacheTime));
191
+ const getCacheFn = customGetCache || getCache;
176
192
  const getKey = () => {
177
193
  const key = typeof cacheKey === "function" ? cacheKey() : cacheKey;
178
194
  return key;
@@ -182,16 +198,35 @@ function useRequestSWR(cacheKey, service, options = {}) {
182
198
  data,
183
199
  error,
184
200
  params,
185
- run,
201
+ run: originalRun,
186
202
  mutate,
187
203
  cancel,
188
- refresh,
189
204
  disabled
190
- } = useRequest(key => service(key), {
205
+ } = useRequest((key, options2) => service(key, options2), {
191
206
  manual,
192
207
  defaultParams: manual ? [] : [getKey()],
193
208
  ...requestOptions
194
209
  });
210
+ const initialKey = getKey();
211
+ if (initialKey) {
212
+ const cachedVal = getCacheFn(initialKey);
213
+ if (cachedVal !== void 0) {
214
+ data.value = cachedVal;
215
+ }
216
+ }
217
+ const swrRun = async key => {
218
+ const cachedVal = getCacheFn(key);
219
+ if (cachedVal !== void 0) {
220
+ data.value = cachedVal;
221
+ }
222
+ return originalRun(key);
223
+ };
224
+ const swrRefresh = async () => {
225
+ const key = params.value[0] || getKey();
226
+ if (key) {
227
+ await swrRun(key).catch(() => {});
228
+ }
229
+ };
195
230
  const updateCache = newData => {
196
231
  const key = getKey();
197
232
  if (key) {
@@ -205,27 +240,31 @@ function useRequestSWR(cacheKey, service, options = {}) {
205
240
  }, {
206
241
  immediate: true
207
242
  });
208
- if (refreshOnWindowFocus && !manual) {
243
+ if (refreshOnWindowFocus && !manual && (0, _vue.getCurrentInstance)()) {
244
+ const handleFocus = () => {
245
+ const key = getKey();
246
+ if (key && !loading.value) {
247
+ swrRefresh();
248
+ }
249
+ };
209
250
  (0, _vue.onMounted)(() => {
210
- const handleFocus = () => {
211
- const key = getKey();
212
- if (key && !loading.value) {
213
- refresh();
214
- }
215
- };
216
251
  window.addEventListener("visibilitychange", handleFocus);
217
252
  window.addEventListener("focus", handleFocus);
218
- (0, _vue.onUnmounted)(() => {
219
- window.removeEventListener("visibilitychange", handleFocus);
220
- window.removeEventListener("focus", handleFocus);
221
- });
253
+ });
254
+ (0, _vue.onUnmounted)(() => {
255
+ window.removeEventListener("visibilitychange", handleFocus);
256
+ window.removeEventListener("focus", handleFocus);
222
257
  });
223
258
  }
224
259
  if (refreshDeps && refreshDeps.length > 0 && !manual) {
225
260
  (0, _vue.watch)(() => refreshDeps.map(dep => dep.value), () => {
226
261
  const key = getKey();
227
262
  if (key) {
228
- setTimeout(() => run(key).catch(() => {}), refreshDepsWait);
263
+ const cachedVal = getCacheFn(key);
264
+ if (cachedVal !== void 0) {
265
+ data.value = cachedVal;
266
+ }
267
+ setTimeout(() => swrRun(key).catch(() => {}), refreshDepsWait);
229
268
  }
230
269
  }, {
231
270
  deep: true
@@ -238,10 +277,10 @@ function useRequestSWR(cacheKey, service, options = {}) {
238
277
  params,
239
278
  loadingMore: (0, _vue.ref)(false),
240
279
  noMore: (0, _vue.ref)(false),
241
- run,
280
+ run: swrRun,
242
281
  mutate,
243
282
  cancel,
244
- refresh,
283
+ refresh: swrRefresh,
245
284
  loadMore: async () => {
246
285
  return Promise.resolve();
247
286
  },
@@ -14,7 +14,9 @@ export interface UseRequestOptions<TData, TParams extends unknown[] = unknown[]>
14
14
  /** 请求实例 */
15
15
  request?: Request;
16
16
  /** 请求函数 */
17
- service?: (...args: TParams) => Promise<RequestResponse<TData>>;
17
+ service?: (...args: [...TParams, {
18
+ signal?: AbortSignal;
19
+ }?] | unknown[]) => Promise<RequestResponse<TData>>;
18
20
  /** 格式化返回数据 */
19
21
  formatResult?: (response: RequestResponse<TData>) => TData;
20
22
  /** 成功回调 */
@@ -108,7 +110,9 @@ export declare function useRequest<TData = unknown, TParams extends unknown[] =
108
110
  * { cacheKey: 'user' }
109
111
  * )
110
112
  */
111
- export declare function useRequestSWR<TData = unknown>(cacheKey: string | (() => string), service: (key: string) => Promise<RequestResponse<TData>>, options?: UseRequestSWROptions<TData, [string]>): UseRequestReturn<TData, [string]>;
113
+ export declare function useRequestSWR<TData = unknown>(cacheKey: string | (() => string), service: (key: string, options?: {
114
+ signal?: AbortSignal;
115
+ }) => Promise<RequestResponse<TData>>, options?: UseRequestSWROptions<TData, [string]>): UseRequestReturn<TData, [string]>;
112
116
  /**
113
117
  * useRequestPolling - 带轮询的请求 Hook
114
118
  *
@@ -4,7 +4,8 @@ import {
4
4
  computed,
5
5
  watch,
6
6
  onMounted,
7
- onUnmounted
7
+ onUnmounted,
8
+ getCurrentInstance
8
9
  } from "vue";
9
10
  import { debounce, throttle } from "@yh-ui/utils";
10
11
  const globalCache = /* @__PURE__ */ new Map();
@@ -12,6 +13,16 @@ function setCache(key, value, cacheTime) {
12
13
  const expireTime = Date.now() + (cacheTime || 5 * 60 * 1e3);
13
14
  globalCache.set(key, { data: value, expireTime });
14
15
  }
16
+ function getCache(key) {
17
+ const cached = globalCache.get(key);
18
+ if (cached) {
19
+ if (Date.now() < cached.expireTime) {
20
+ return cached.data;
21
+ }
22
+ globalCache.delete(key);
23
+ }
24
+ return void 0;
25
+ }
15
26
  export function useRequest(service, options = {}) {
16
27
  const {
17
28
  manual = false,
@@ -51,7 +62,7 @@ export function useRequest(service, options = {}) {
51
62
  loading.value = true;
52
63
  error.value = void 0;
53
64
  try {
54
- const response = await service(...runParams);
65
+ const response = await service(...runParams, { signal: abortController.signal });
55
66
  if (currentRunCount !== runCount.value) {
56
67
  const cancelError = new Error("Request canceled");
57
68
  cancelError.isCanceled = true;
@@ -91,10 +102,12 @@ export function useRequest(service, options = {}) {
91
102
  }
92
103
  };
93
104
  const cancel = () => {
105
+ runCount.value++;
94
106
  if (abortController) {
95
107
  abortController.abort();
96
108
  abortController = null;
97
109
  }
110
+ cancelFn?.();
98
111
  loading.value = false;
99
112
  };
100
113
  const refresh = async () => {
@@ -173,6 +186,7 @@ export function useRequestSWR(cacheKey, service, options = {}) {
173
186
  const {
174
187
  cacheTime = 10 * 60 * 1e3,
175
188
  setCache: customSetCache,
189
+ getCache: customGetCache,
176
190
  refreshOnWindowFocus = false,
177
191
  refreshDepsWait = 1e3,
178
192
  refreshDeps = [],
@@ -180,15 +194,49 @@ export function useRequestSWR(cacheKey, service, options = {}) {
180
194
  ...requestOptions
181
195
  } = options;
182
196
  const setCacheFn = customSetCache || ((key, value) => setCache(key, value, cacheTime));
197
+ const getCacheFn = customGetCache || getCache;
183
198
  const getKey = () => {
184
199
  const key = typeof cacheKey === "function" ? cacheKey() : cacheKey;
185
200
  return key;
186
201
  };
187
- const { loading, data, error, params, run, mutate, cancel, refresh, disabled } = useRequest((key) => service(key), {
188
- manual,
189
- defaultParams: manual ? [] : [getKey()],
190
- ...requestOptions
191
- });
202
+ const {
203
+ loading,
204
+ data,
205
+ error,
206
+ params,
207
+ run: originalRun,
208
+ mutate,
209
+ cancel,
210
+ disabled
211
+ } = useRequest(
212
+ (key, options2) => service(key, options2),
213
+ {
214
+ manual,
215
+ defaultParams: manual ? [] : [getKey()],
216
+ ...requestOptions
217
+ }
218
+ );
219
+ const initialKey = getKey();
220
+ if (initialKey) {
221
+ const cachedVal = getCacheFn(initialKey);
222
+ if (cachedVal !== void 0) {
223
+ data.value = cachedVal;
224
+ }
225
+ }
226
+ const swrRun = async (key) => {
227
+ const cachedVal = getCacheFn(key);
228
+ if (cachedVal !== void 0) {
229
+ data.value = cachedVal;
230
+ }
231
+ return originalRun(key);
232
+ };
233
+ const swrRefresh = async () => {
234
+ const key = params.value[0] || getKey();
235
+ if (key) {
236
+ await swrRun(key).catch(() => {
237
+ });
238
+ }
239
+ };
192
240
  const updateCache = (newData) => {
193
241
  const key = getKey();
194
242
  if (key) {
@@ -204,20 +252,20 @@ export function useRequestSWR(cacheKey, service, options = {}) {
204
252
  },
205
253
  { immediate: true }
206
254
  );
207
- if (refreshOnWindowFocus && !manual) {
255
+ if (refreshOnWindowFocus && !manual && getCurrentInstance()) {
256
+ const handleFocus = () => {
257
+ const key = getKey();
258
+ if (key && !loading.value) {
259
+ swrRefresh();
260
+ }
261
+ };
208
262
  onMounted(() => {
209
- const handleFocus = () => {
210
- const key = getKey();
211
- if (key && !loading.value) {
212
- refresh();
213
- }
214
- };
215
263
  window.addEventListener("visibilitychange", handleFocus);
216
264
  window.addEventListener("focus", handleFocus);
217
- onUnmounted(() => {
218
- window.removeEventListener("visibilitychange", handleFocus);
219
- window.removeEventListener("focus", handleFocus);
220
- });
265
+ });
266
+ onUnmounted(() => {
267
+ window.removeEventListener("visibilitychange", handleFocus);
268
+ window.removeEventListener("focus", handleFocus);
221
269
  });
222
270
  }
223
271
  if (refreshDeps && refreshDeps.length > 0 && !manual) {
@@ -226,7 +274,11 @@ export function useRequestSWR(cacheKey, service, options = {}) {
226
274
  () => {
227
275
  const key = getKey();
228
276
  if (key) {
229
- setTimeout(() => run(key).catch(() => {
277
+ const cachedVal = getCacheFn(key);
278
+ if (cachedVal !== void 0) {
279
+ data.value = cachedVal;
280
+ }
281
+ setTimeout(() => swrRun(key).catch(() => {
230
282
  }), refreshDepsWait);
231
283
  }
232
284
  },
@@ -240,10 +292,10 @@ export function useRequestSWR(cacheKey, service, options = {}) {
240
292
  params,
241
293
  loadingMore: ref(false),
242
294
  noMore: ref(false),
243
- run,
295
+ run: swrRun,
244
296
  mutate,
245
297
  cancel,
246
- refresh,
298
+ refresh: swrRefresh,
247
299
  loadMore: async () => {
248
300
  return Promise.resolve();
249
301
  },
@@ -20,6 +20,9 @@ class WebSocketClient {
20
20
  heartbeatTimer = null;
21
21
  heartbeatTimeoutTimer = null;
22
22
  messageQueue = [];
23
+ isActivelyClosed = false;
24
+ messageIdCounter = 0;
25
+ pendingRequests = /* @__PURE__ */new Map();
23
26
  // 回调
24
27
  onOpenCallback = null;
25
28
  onCloseCallback = null;
@@ -84,6 +87,7 @@ class WebSocketClient {
84
87
  * 连接
85
88
  */
86
89
  connect() {
90
+ this.isActivelyClosed = false;
87
91
  return new Promise((resolve, reject) => {
88
92
  if (!isWebSocketSupported()) {
89
93
  const error = new Error("WebSocket is not supported in this environment");
@@ -118,6 +122,7 @@ class WebSocketClient {
118
122
  * 断开连接
119
123
  */
120
124
  disconnect(code = 1e3, reason = "Client disconnect") {
125
+ this.isActivelyClosed = true;
121
126
  this.clearTimers();
122
127
  this.setState("disconnecting");
123
128
  if (this.ws) {
@@ -125,6 +130,7 @@ class WebSocketClient {
125
130
  this.ws = null;
126
131
  }
127
132
  this.setState("disconnected");
133
+ this.rejectAllPendingRequests("WebSocket disconnected: " + reason);
128
134
  }
129
135
  /**
130
136
  * 发送消息
@@ -143,25 +149,24 @@ class WebSocketClient {
143
149
  */
144
150
  sendAndWait(data, timeout = 3e4) {
145
151
  return new Promise((resolve, reject) => {
146
- const messageId = Date.now().toString();
152
+ const messageId = `msg_${Date.now()}_${++this.messageIdCounter}`;
147
153
  const timer = setTimeout(() => {
154
+ this.pendingRequests.delete(messageId);
148
155
  reject(new Error("WebSocket message timeout"));
149
156
  }, timeout);
150
- const originalCallback = this.onMessageCallback;
151
- this.onMessageCallback = message => {
152
- const decoded = message.data;
153
- if (decoded && decoded.id === messageId) {
154
- clearTimeout(timer);
155
- this.onMessageCallback = originalCallback;
156
- resolve(decoded.result);
157
- } else {
158
- originalCallback?.(message);
159
- }
160
- };
161
- this.send({
157
+ this.pendingRequests.set(messageId, {
158
+ resolve,
159
+ reject,
160
+ timer
161
+ });
162
+ const payload = typeof data === "object" && data !== null ? {
162
163
  id: messageId,
163
164
  ...data
164
- });
165
+ } : {
166
+ id: messageId,
167
+ data
168
+ };
169
+ this.send(payload);
165
170
  });
166
171
  }
167
172
  /**
@@ -206,9 +211,10 @@ class WebSocketClient {
206
211
  this.ws = null;
207
212
  this.clearTimers();
208
213
  this.setState("disconnected");
209
- if (this.options.reconnect && this.reconnectAttempts < this.options.reconnectMaxAttempts) {
214
+ if (!this.isActivelyClosed && this.options.reconnect && this.reconnectAttempts < this.options.reconnectMaxAttempts) {
210
215
  this.reconnect();
211
216
  }
217
+ this.rejectAllPendingRequests(`WebSocket disconnected: ${event.reason || "Closed"}`);
212
218
  this.onCloseCallback?.(event.code, event.reason);
213
219
  }
214
220
  handleError(event) {
@@ -234,6 +240,18 @@ class WebSocketClient {
234
240
  timestamp: Date.now()
235
241
  };
236
242
  this.lastMessage.value = message;
243
+ if (data && typeof data === "object") {
244
+ const decoded = data;
245
+ if (decoded.id && this.pendingRequests.has(decoded.id)) {
246
+ const pending = this.pendingRequests.get(decoded.id);
247
+ if (pending) {
248
+ clearTimeout(pending.timer);
249
+ this.pendingRequests.delete(decoded.id);
250
+ pending.resolve(decoded.result);
251
+ return;
252
+ }
253
+ }
254
+ }
237
255
  this.onMessageCallback?.(message);
238
256
  }
239
257
  reconnect() {
@@ -271,6 +289,13 @@ class WebSocketClient {
271
289
  }
272
290
  this.clearHeartbeatTimeout();
273
291
  }
292
+ rejectAllPendingRequests(reason = "WebSocket disconnected") {
293
+ for (const [, pending] of this.pendingRequests.entries()) {
294
+ clearTimeout(pending.timer);
295
+ pending.reject(new Error(reason));
296
+ }
297
+ this.pendingRequests.clear();
298
+ }
274
299
  /**
275
300
  * 清理资源
276
301
  */
@@ -62,6 +62,9 @@ export declare class WebSocketClient {
62
62
  private heartbeatTimer;
63
63
  private heartbeatTimeoutTimer;
64
64
  private messageQueue;
65
+ private isActivelyClosed;
66
+ private messageIdCounter;
67
+ private pendingRequests;
65
68
  private onOpenCallback;
66
69
  private onCloseCallback;
67
70
  private onErrorCallback;
@@ -111,6 +114,7 @@ export declare class WebSocketClient {
111
114
  private startHeartbeat;
112
115
  private clearHeartbeatTimeout;
113
116
  private clearTimers;
117
+ private rejectAllPendingRequests;
114
118
  /**
115
119
  * 清理资源
116
120
  */
@@ -11,6 +11,9 @@ export class WebSocketClient {
11
11
  heartbeatTimer = null;
12
12
  heartbeatTimeoutTimer = null;
13
13
  messageQueue = [];
14
+ isActivelyClosed = false;
15
+ messageIdCounter = 0;
16
+ pendingRequests = /* @__PURE__ */ new Map();
14
17
  // 回调
15
18
  onOpenCallback = null;
16
19
  onCloseCallback = null;
@@ -75,6 +78,7 @@ export class WebSocketClient {
75
78
  * 连接
76
79
  */
77
80
  connect() {
81
+ this.isActivelyClosed = false;
78
82
  return new Promise((resolve, reject) => {
79
83
  if (!isWebSocketSupported()) {
80
84
  const error = new Error("WebSocket is not supported in this environment");
@@ -109,6 +113,7 @@ export class WebSocketClient {
109
113
  * 断开连接
110
114
  */
111
115
  disconnect(code = 1e3, reason = "Client disconnect") {
116
+ this.isActivelyClosed = true;
112
117
  this.clearTimers();
113
118
  this.setState("disconnecting");
114
119
  if (this.ws) {
@@ -116,6 +121,7 @@ export class WebSocketClient {
116
121
  this.ws = null;
117
122
  }
118
123
  this.setState("disconnected");
124
+ this.rejectAllPendingRequests("WebSocket disconnected: " + reason);
119
125
  }
120
126
  /**
121
127
  * 发送消息
@@ -134,22 +140,14 @@ export class WebSocketClient {
134
140
  */
135
141
  sendAndWait(data, timeout = 3e4) {
136
142
  return new Promise((resolve, reject) => {
137
- const messageId = Date.now().toString();
143
+ const messageId = `msg_${Date.now()}_${++this.messageIdCounter}`;
138
144
  const timer = setTimeout(() => {
145
+ this.pendingRequests.delete(messageId);
139
146
  reject(new Error("WebSocket message timeout"));
140
147
  }, timeout);
141
- const originalCallback = this.onMessageCallback;
142
- this.onMessageCallback = (message) => {
143
- const decoded = message.data;
144
- if (decoded && decoded.id === messageId) {
145
- clearTimeout(timer);
146
- this.onMessageCallback = originalCallback;
147
- resolve(decoded.result);
148
- } else {
149
- originalCallback?.(message);
150
- }
151
- };
152
- this.send({ id: messageId, ...data });
148
+ this.pendingRequests.set(messageId, { resolve, reject, timer });
149
+ const payload = typeof data === "object" && data !== null ? { id: messageId, ...data } : { id: messageId, data };
150
+ this.send(payload);
153
151
  });
154
152
  }
155
153
  /**
@@ -194,9 +192,10 @@ export class WebSocketClient {
194
192
  this.ws = null;
195
193
  this.clearTimers();
196
194
  this.setState("disconnected");
197
- if (this.options.reconnect && this.reconnectAttempts < this.options.reconnectMaxAttempts) {
195
+ if (!this.isActivelyClosed && this.options.reconnect && this.reconnectAttempts < this.options.reconnectMaxAttempts) {
198
196
  this.reconnect();
199
197
  }
198
+ this.rejectAllPendingRequests(`WebSocket disconnected: ${event.reason || "Closed"}`);
200
199
  this.onCloseCallback?.(event.code, event.reason);
201
200
  }
202
201
  handleError(event) {
@@ -222,6 +221,18 @@ export class WebSocketClient {
222
221
  timestamp: Date.now()
223
222
  };
224
223
  this.lastMessage.value = message;
224
+ if (data && typeof data === "object") {
225
+ const decoded = data;
226
+ if (decoded.id && this.pendingRequests.has(decoded.id)) {
227
+ const pending = this.pendingRequests.get(decoded.id);
228
+ if (pending) {
229
+ clearTimeout(pending.timer);
230
+ this.pendingRequests.delete(decoded.id);
231
+ pending.resolve(decoded.result);
232
+ return;
233
+ }
234
+ }
235
+ }
225
236
  this.onMessageCallback?.(message);
226
237
  }
227
238
  reconnect() {
@@ -263,6 +274,13 @@ export class WebSocketClient {
263
274
  }
264
275
  this.clearHeartbeatTimeout();
265
276
  }
277
+ rejectAllPendingRequests(reason = "WebSocket disconnected") {
278
+ for (const [, pending] of this.pendingRequests.entries()) {
279
+ clearTimeout(pending.timer);
280
+ pending.reject(new Error(reason));
281
+ }
282
+ this.pendingRequests.clear();
283
+ }
266
284
  /**
267
285
  * 清理资源
268
286
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yh-ui/request",
3
- "version": "1.0.52",
3
+ "version": "1.0.53",
4
4
  "description": "YH-UI HTTP request hooks - Enterprise-grade request management",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -32,7 +32,7 @@
32
32
  "postpack": "node ../../scripts/prepare-package-manifest.mjs restore"
33
33
  },
34
34
  "dependencies": {
35
- "@yh-ui/utils": "^1.0.52"
35
+ "@yh-ui/utils": "^1.0.53"
36
36
  },
37
37
  "devDependencies": {
38
38
  "vue": "^3.5.35",