-
Notifications
You must be signed in to change notification settings - Fork 95
/
index.ts
104 lines (92 loc) · 2.91 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import {useMemo, useRef} from 'react';
import {
type AsyncState,
useAsync,
type UseAsyncActions,
type UseAsyncMeta,
} from '../useAsync/index.js';
export type UseAsyncAbortableActions<Result, Args extends unknown[] = unknown[]> = {
/**
* Abort the currently running async function invocation.
*/
abort: () => void;
/**
* Abort the currently running async function invocation and reset state to initial.
*/
reset: () => void;
} & UseAsyncActions<Result, Args>;
export type UseAsyncAbortableMeta<Result, Args extends unknown[] = unknown[]> = {
/**
* Currently used `AbortController`. New one is created on each execution of the async function.
*/
abortController: AbortController | undefined;
} & UseAsyncMeta<Result, Args>;
export type ArgsWithAbortSignal<Args extends unknown[] = unknown[]> = [AbortSignal, ...Args];
export function useAsyncAbortable<Result, Args extends unknown[] = unknown[]>(
asyncFn: (...params: ArgsWithAbortSignal<Args>) => Promise<Result>,
initialValue: Result
): [
AsyncState<Result>,
UseAsyncAbortableActions<Result, Args>,
UseAsyncAbortableMeta<Result, Args>,
];
export function useAsyncAbortable<Result, Args extends unknown[] = unknown[]>(
asyncFn: (...params: ArgsWithAbortSignal<Args>) => Promise<Result>,
initialValue?: Result
): [
AsyncState<Result | undefined>,
UseAsyncAbortableActions<Result, Args>,
UseAsyncAbortableMeta<Result, Args>,
];
/**
* Like `useAsync`, but also provides `AbortSignal` as the first argument to the async function.
*
* @param asyncFn Function that returns a promise.
* @param initialValue Value that will be set on initialisation before the async function is
* executed.
*/
export function useAsyncAbortable<Result, Args extends unknown[] = unknown[]>(
asyncFn: (...params: ArgsWithAbortSignal<Args>) => Promise<Result>,
initialValue?: Result,
): [
AsyncState<Result | undefined>,
UseAsyncAbortableActions<Result, Args>,
UseAsyncAbortableMeta<Result, Args>,
] {
const abortController = useRef<AbortController>();
const fn = async (...args: Args): Promise<Result> => {
// Abort previous async
abortController.current?.abort();
// Create new controller for ongoing async call
const ac = new AbortController();
abortController.current = ac;
// Pass down abort signal and received arguments
return asyncFn(ac.signal, ...args).finally(() => {
// Unset ref uf the call is last
if (abortController.current === ac) {
abortController.current = undefined;
}
});
};
const [state, asyncActions, asyncMeta] = useAsync<Result, Args>(fn, initialValue);
return [
state,
useMemo(() => {
const actions = {
reset() {
actions.abort();
asyncActions.reset();
},
abort() {
abortController.current?.abort();
},
};
return {
...asyncActions,
...actions,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
{...asyncMeta, abortController: abortController.current},
];
}