-
Notifications
You must be signed in to change notification settings - Fork 105
Expand file tree
/
Copy pathindex.ts
More file actions
89 lines (77 loc) · 2.97 KB
/
index.ts
File metadata and controls
89 lines (77 loc) · 2.97 KB
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
import {useMemo, useRef} from 'react';
import type {AsyncState, UseAsyncActions, UseAsyncMeta} from '../useAsync/index.js';
import {useAsync} 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>(undefined);
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
// eslint-disable-next-line promise/prefer-await-to-then
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},
];
}