cancel requests if unmounted before completion
Massimo Melina committed
Feb 3, 2023 at 18:43 UTC
06612c230e5c4df6114415e2bb014c80d41b55b9
1 file changed
+18
-7
admin/src/api.ts
+18
-7
@@ -27,7 +27,7 @@ const PREFIX = '/~/api/'
27
const timeoutByApi: Dict = {
28
get_status: 20 // can be lengthy on slow machines because of the find-process-on-busy-port feature
29
}
30
-export function apiCall(cmd: string, params?: Dict, { timeout=undefined }={}) : Promise<any> {
30
+export function apiCall(cmd: string, params?: Dict, { timeout=undefined }={}) {
31
const csrf = getCsrf()
32
if (csrf)
33
params = { csrf, ...params }
@@ -35,7 +35,7 @@ export function apiCall(cmd: string, params?: Dict, { timeout=undefined }={}) :
35
const controller = new AbortController()
36
if (timeout !== false)
37
setTimeout(() => controller.abort('timeout'), 1000*(timeoutByApi[cmd] ?? timeout ?? 10))
38
- return fetch(PREFIX+cmd, {
38
+ return Object.assign(fetch(PREFIX+cmd, {
39
method: 'POST',
40
headers: { 'content-type': 'application/json' },
41
signal: controller.signal,
@@ -55,6 +55,10 @@ export function apiCall(cmd: string, params?: Dict, { timeout=undefined }={}) :
55
if (err?.message?.includes('fetch'))
56
throw Error("Network error")
57
throw err
58
+ }), {
59
+ abort() {
60
+ controller.abort('cancel')
61
+ }
62
})
63
}
64
@@ -68,15 +72,22 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object) : [T | undef
72
const [ret, setRet] = useStateMounted<T | undefined>(undefined)
73
const [err, setErr] = useStateMounted<Error | undefined>(undefined)
74
const [forcer, setForcer] = useStateMounted(0)
71
- const loadingRef = useRef(false)
75
+ const loadingRef = useRef<ReturnType<typeof apiCall>>()
76
useEffect(()=>{
77
+ loadingRef.current?.abort()
78
setRet(undefined)
79
setErr(undefined)
80
if (!cmd) return
76
- loadingRef.current = true
77
- apiCall(cmd, params)
78
- .then(setRet, setErr)
79
- .finally(()=> loadingRef.current = false)
81
+ let aborted = false
82
+ const req = apiCall(cmd, params)
83
+ const wholePromise = req.then(x => aborted || setRet(x), x => aborted || setErr(x))
84
+ .finally(()=> loadingRef.current = undefined)
85
+ loadingRef.current = Object.assign(wholePromise, {
86
+ abort() {
87
+ aborted = true
88
+ req.abort()
89
+ }
90
+ })
91
}, [cmd, JSON.stringify(params), forcer]) //eslint-disable-line -- json-ize to detect deep changes
92
const reload = useCallback(()=> loadingRef.current || setForcer(v => v+1), [setForcer])
93
return [ret, err, reload]