@@ -9,8 +9,10 @@ type SetLoading<R> = (asyncState: AsyncState<R>) => AsyncState<R>;
9
9
type SetResult < R > = ( result : R , asyncState : AsyncState < R > ) => AsyncState < R > ;
10
10
type SetError < R > = ( error : Error , asyncState : AsyncState < R > ) => AsyncState < R > ;
11
11
12
+ type MaybePromise < T > = Promise < T > | T ;
13
+
12
14
export type UseAsyncOptionsNormalized < R > = {
13
- initialState : AsyncState < R > ;
15
+ initialState : ( ) => AsyncState < R > ;
14
16
executeOnMount : boolean ;
15
17
executeOnUpdate : boolean ;
16
18
setLoading : SetLoading < R > ;
@@ -43,7 +45,7 @@ const defaultSetError: SetError<any> = (error, _asyncState) => ({
43
45
} ) ;
44
46
45
47
const DefaultOptions = {
46
- initialState : InitialAsyncState ,
48
+ initialState : ( ) => InitialAsyncState ,
47
49
executeOnMount : true ,
48
50
executeOnUpdate : true ,
49
51
setLoading : defaultSetLoading ,
@@ -103,16 +105,19 @@ const useCurrentPromise = <R>(): UseCurrentPromiseReturn<R> => {
103
105
} ;
104
106
} ;
105
107
106
- export type UseAsyncReturn < R > = AsyncState < R > & {
108
+ export type UseAsyncReturn < R , Args extends any [ ] > = AsyncState < R > & {
107
109
set : ( value : AsyncState < R > ) => void ;
108
- execute : ( ) => Promise < R > ;
110
+ execute : ( ... args : Args ) => Promise < R > ;
109
111
currentPromise : Promise < R > | null ;
110
112
} ;
111
- export const useAsync = < R , Args extends any [ ] > (
112
- asyncFunction : ( ...args : Args ) => Promise < R > ,
113
+
114
+ // Relaxed interface which accept both async and sync functions
115
+ // Accepting sync function is convenient for useAsyncCallback
116
+ const useAsyncInternal = < R , Args extends any [ ] > (
117
+ asyncFunction : ( ...args : Args ) => MaybePromise < R > ,
113
118
params : Args ,
114
119
options ?: UseAsyncOptions < R >
115
- ) : UseAsyncReturn < R > => {
120
+ ) : UseAsyncReturn < R , Args > => {
116
121
const normalizedOptions = normalizeOptions < R > ( options ) ;
117
122
118
123
const AsyncState = useAsyncState < R > ( normalizedOptions ) ;
@@ -125,33 +130,40 @@ export const useAsync = <R, Args extends any[]>(
125
130
const shouldHandlePromise = ( p : Promise < R > ) =>
126
131
isMounted ( ) && CurrentPromise . is ( p ) ;
127
132
128
- const executeAsyncOperation = ( ) : Promise < R > => {
129
- const promise = asyncFunction ( ...params ) ;
130
- CurrentPromise . set ( promise ) ;
131
- AsyncState . setLoading ( ) ;
132
- promise . then (
133
- result => {
134
- if ( shouldHandlePromise ( promise ) ) {
135
- AsyncState . setResult ( result ) ;
133
+ const executeAsyncOperation = ( ...args : Args ) : Promise < R > => {
134
+ const promise : MaybePromise < R > = asyncFunction ( ...args ) ;
135
+ if ( promise instanceof Promise ) {
136
+ CurrentPromise . set ( promise ) ;
137
+ AsyncState . setLoading ( ) ;
138
+ promise . then (
139
+ result => {
140
+ if ( shouldHandlePromise ( promise ) ) {
141
+ AsyncState . setResult ( result ) ;
142
+ }
143
+ } ,
144
+ error => {
145
+ if ( shouldHandlePromise ( promise ) ) {
146
+ AsyncState . setError ( error ) ;
147
+ }
136
148
}
137
- } ,
138
- error => {
139
- if ( shouldHandlePromise ( promise ) ) {
140
- AsyncState . setError ( error ) ;
141
- }
142
- }
143
- ) ;
144
- return promise ;
149
+ ) ;
150
+ return promise ;
151
+ } else {
152
+ // We allow passing a non-async function (mostly for useAsyncCallback conveniency)
153
+ const syncResult : R = promise ;
154
+ AsyncState . setResult ( syncResult ) ;
155
+ return Promise . resolve < R > ( syncResult ) ;
156
+ }
145
157
} ;
146
158
147
159
// Keep this outside useEffect, because inside isMounted()
148
160
// will be true as the component is already mounted when it's run
149
161
const isMounting = ! isMounted ( ) ;
150
162
useEffect ( ( ) => {
151
163
if ( isMounting ) {
152
- normalizedOptions . executeOnMount && executeAsyncOperation ( ) ;
164
+ normalizedOptions . executeOnMount && executeAsyncOperation ( ... params ) ;
153
165
} else {
154
- normalizedOptions . executeOnUpdate && executeAsyncOperation ( ) ;
166
+ normalizedOptions . executeOnUpdate && executeAsyncOperation ( ... params ) ;
155
167
}
156
168
} , params ) ;
157
169
@@ -163,6 +175,12 @@ export const useAsync = <R, Args extends any[]>(
163
175
} ;
164
176
} ;
165
177
178
+ export const useAsync = < R , Args extends any [ ] > (
179
+ asyncFunction : ( ...args : Args ) => Promise < R > ,
180
+ params : Args ,
181
+ options ?: UseAsyncOptions < R >
182
+ ) : UseAsyncReturn < R , Args > => useAsync ( asyncFunction , params , options ) ;
183
+
166
184
type AddArg < H , T extends any [ ] > = ( ( h : H , ...t : T ) => void ) extends ( (
167
185
...r : infer R
168
186
) => void )
@@ -173,7 +191,7 @@ export const useAsyncAbortable = <R, Args extends any[]>(
173
191
asyncFunction : ( ...args : AddArg < AbortSignal , Args > ) => Promise < R > ,
174
192
params : Args ,
175
193
options ?: UseAsyncOptions < R >
176
- ) : UseAsyncReturn < R > => {
194
+ ) : UseAsyncReturn < R , Args > => {
177
195
const abortControllerRef = useRef < AbortController > ( ) ;
178
196
179
197
// Wrap the original async function and enhance it with abortion login
@@ -202,3 +220,23 @@ export const useAsyncAbortable = <R, Args extends any[]>(
202
220
203
221
return useAsync ( asyncFunctionWrapper , params , options ) ;
204
222
} ;
223
+
224
+ export const useAsyncCallback = < R , Args extends any [ ] > (
225
+ asyncFunction : ( ...args : Args ) => MaybePromise < R >
226
+ ) : UseAsyncReturn < R , Args > => {
227
+ return useAsyncInternal (
228
+ asyncFunction ,
229
+ // Hacky but in such case we don't need the params,
230
+ // because async function is only executed manually
231
+ [ ] as any ,
232
+ {
233
+ executeOnMount : false ,
234
+ executeOnUpdate : false ,
235
+ initialState : ( ) => ( {
236
+ loading : false ,
237
+ result : undefined ,
238
+ error : undefined ,
239
+ } ) ,
240
+ }
241
+ ) ;
242
+ } ;
0 commit comments