Skip to content

Commit 4ef1944

Browse files
committed
🐛 [useRequest]: Fix production-level defects in Fetch
- Fix null params handling: clear old data and call plugin callbacks - Fix cancel logic: clear error state when cancelling - Fix plugin exception isolation: catch exceptions to prevent cascade failures - Fix callback exception protection: preserve original errors - Fix onFinally execution: always execute in finally block for resource cleanup - Fix mutate exception safety: prevent state corruption All 15 edge case tests now pass (100% pass rate)
1 parent 50a4dd7 commit 4ef1944

2 files changed

Lines changed: 564 additions & 64 deletions

File tree

  • hooks/src

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/userequest/Fetch.kt

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,13 @@ class Fetch<TParams, TData : Any>(private val options: UseRequestOptions<TParams
123123
setState(
124124
Keys.loading to false,
125125
Keys.params to null,
126+
Keys.data to null, // 清除旧数据
126127
Keys.error to error,
127128
)
128129
options.onError.invoke(error, null)
130+
runPluginHandler(Methods.OnError(error, null)) // 调用插件的 onError
129131
options.onFinally.invoke(null, null, error)
132+
runPluginHandler(Methods.OnFinally(null, null, error)) // 调用插件的 onFinally
130133
return@coroutineScope
131134
}
132135
count += 1
@@ -157,6 +160,8 @@ class Fetch<TParams, TData : Any>(private val options: UseRequestOptions<TParams
157160
// 调用选项配置的生命周期函数
158161
options.onBefore.invoke(latestParams)
159162

163+
var requestResult: TData? = null
164+
var requestError: Throwable? = null
160165
try {
161166
var (serviceDeferred) = OnRequestReturn<TData>().copy(
162167
(
@@ -169,30 +174,40 @@ class Fetch<TParams, TData : Any>(private val options: UseRequestOptions<TParams
169174
serviceDeferred = serviceDeferred ?: async(SupervisorJob()) { requestFn(latestParams!!) }
170175
val result = serviceDeferred.awaitPlus()
171176
if (currentCount != count) return@coroutineScope
177+
requestResult = result
172178
setState(
173179
Keys.loading to false,
174180
Keys.data to result,
175181
Keys.error to null,
176182
)
177-
options.onSuccess.invoke(result, latestParams)
178-
runPluginHandler(Methods.OnSuccess(result, latestParams))
179-
// 回调finally
180-
options.onFinally.invoke(latestParams, result, null)
181-
if (currentCount == count) {
182-
runPluginHandler(Methods.OnFinally(latestParams, result, null))
183+
try {
184+
options.onSuccess.invoke(result, latestParams)
185+
} catch (e: Throwable) {
186+
// 捕获 onSuccess 回调异常,防止影响后续流程
183187
}
188+
runPluginHandler(Methods.OnSuccess(result, latestParams))
184189
} catch (error: Throwable) {
185190
if (currentCount != count) return@coroutineScope
191+
requestError = error
186192
setState(
187193
Keys.loading to false,
188194
Keys.error to error,
189195
)
190-
options.onError.invoke(error, latestParams)
196+
try {
197+
options.onError.invoke(error, latestParams)
198+
} catch (e: Throwable) {
199+
// 捕获 onError 回调异常,保留原始错误
200+
}
191201
runPluginHandler(Methods.OnError(error, latestParams))
192-
options.onFinally.invoke(latestParams, null, error)
193-
if (currentCount == count) {
194-
runPluginHandler(Methods.OnFinally(latestParams, null, error))
202+
} finally {
203+
// onFinally 在 finally 块中执行,确保总是被调用
204+
try {
205+
options.onFinally.invoke(latestParams, requestResult, requestError)
206+
} catch (e: Throwable) {
207+
// 捕获 onFinally 回调异常
195208
}
209+
// 插件的 onFinally 应该总是执行,用于清理资源
210+
runPluginHandler(Methods.OnFinally(latestParams, requestResult, requestError))
196211
}
197212
}
198213

@@ -210,7 +225,10 @@ class Fetch<TParams, TData : Any>(private val options: UseRequestOptions<TParams
210225
*/
211226
override fun cancel() {
212227
this.count += 1
213-
this.setState(Keys.loading to false)
228+
this.setState(
229+
Keys.loading to false,
230+
Keys.error to null // 清除错误状态
231+
)
214232
cancelRequest()
215233
runPluginHandler(Methods.OnCancel)
216234
}
@@ -233,66 +251,75 @@ class Fetch<TParams, TData : Any>(private val options: UseRequestOptions<TParams
233251
* 直接修改状态
234252
*/
235253
override fun mutate(mutateFn: (TData?) -> TData) {
236-
val targetData = mutateFn(fetchState.data)
237-
runPluginHandler(Methods.OnMutate(targetData))
238-
setState(Keys.data to targetData)
254+
try {
255+
val targetData = mutateFn(fetchState.data)
256+
runPluginHandler(Methods.OnMutate(targetData))
257+
setState(Keys.data to targetData)
258+
} catch (e: Throwable) {
259+
// 捕获 mutate 函数异常,保持状态一致性
260+
}
239261
}
240262

241263
@Suppress("UNCHECKED_CAST")
242264
private fun runPluginHandler(method: Methods<*, *>): List<*> = pluginImpls.mapNotNull {
243-
when (method) {
244-
is Methods.OnBefore -> {
245-
@Suppress("UNCHECKED_CAST")
246-
it.onBefore?.invoke(method.params as TParams)
247-
}
265+
try {
266+
when (method) {
267+
is Methods.OnBefore -> {
268+
@Suppress("UNCHECKED_CAST")
269+
it.onBefore?.invoke(method.params as TParams)
270+
}
248271

249-
is Methods.OnRequest -> {
250-
it.onRequest?.invoke(
251-
method.requestFn as SuspendNormalFunction<TParams, TData>,
252-
method.params as TParams,
253-
)
254-
}
255-
/**
256-
* 参数1:请求的返回值,参数2:请求使用的参数
257-
*/
258-
is Methods.OnSuccess -> {
259-
@Suppress("UNCHECKED_CAST")
260-
it.onSuccess?.invoke(
261-
method.result as TData,
262-
method.params as TParams,
263-
)
264-
}
265-
/**
266-
* 参数1:错误,参数2:请求使用的参数
267-
*/
268-
is Methods.OnError -> {
269-
@Suppress("UNCHECKED_CAST")
270-
it.onError?.invoke(
271-
method.error,
272-
method.params as TParams,
273-
)
274-
}
275-
/**
276-
* 参数1:请求使用的参数,参数2:请求的返回值,参数3:错误
277-
*/
278-
is Methods.OnFinally -> {
279-
@Suppress("UNCHECKED_CAST")
280-
it.onFinally?.invoke(
281-
method.params as TParams,
282-
method.result as TData?,
283-
method.error,
284-
)
285-
}
272+
is Methods.OnRequest -> {
273+
it.onRequest?.invoke(
274+
method.requestFn as SuspendNormalFunction<TParams, TData>,
275+
method.params as TParams,
276+
)
277+
}
278+
/**
279+
* 参数1:请求的返回值,参数2:请求使用的参数
280+
*/
281+
is Methods.OnSuccess -> {
282+
@Suppress("UNCHECKED_CAST")
283+
it.onSuccess?.invoke(
284+
method.result as TData,
285+
method.params as TParams,
286+
)
287+
}
288+
/**
289+
* 参数1:错误,参数2:请求使用的参数
290+
*/
291+
is Methods.OnError -> {
292+
@Suppress("UNCHECKED_CAST")
293+
it.onError?.invoke(
294+
method.error,
295+
method.params as TParams,
296+
)
297+
}
298+
/**
299+
* 参数1:请求使用的参数,参数2:请求的返回值,参数3:错误
300+
*/
301+
is Methods.OnFinally -> {
302+
@Suppress("UNCHECKED_CAST")
303+
it.onFinally?.invoke(
304+
method.params as TParams,
305+
method.result as TData?,
306+
method.error,
307+
)
308+
}
286309

287-
Methods.OnCancel -> {
288-
it.onCancel?.invoke()
289-
}
290-
/**
291-
* 参数1:要修改的目标数据
292-
*/
293-
is Methods.OnMutate -> {
294-
it.onMutate?.invoke(method.result as TData)
310+
Methods.OnCancel -> {
311+
it.onCancel?.invoke()
312+
}
313+
/**
314+
* 参数1:要修改的目标数据
315+
*/
316+
is Methods.OnMutate -> {
317+
it.onMutate?.invoke(method.result as TData)
318+
}
295319
}
320+
} catch (e: Throwable) {
321+
// 捕获插件异常,防止中断其他插件执行
322+
null
296323
}
297324
}
298325
}

0 commit comments

Comments
 (0)