@@ -29,13 +29,18 @@ import type {
29
29
WithMiddlewareType ,
30
30
TakePattern ,
31
31
ListenerErrorInfo ,
32
- TaskExecutor ,
32
+ ForkedTaskExecutor ,
33
33
ForkedTask ,
34
34
} from './types'
35
-
35
+ import { assertFunction } from './utils'
36
36
import { TaskAbortError } from './exceptions'
37
- import { Outcome } from './outcome'
38
- export type { Outcome , Ok , Error } from './outcome'
37
+ import {
38
+ runTask ,
39
+ promisifyAbortSignal ,
40
+ validateActive ,
41
+ createPause ,
42
+ createDelay ,
43
+ } from './task'
39
44
export { TaskAbortError } from './exceptions'
40
45
export type {
41
46
ActionListener ,
@@ -49,86 +54,68 @@ export type {
49
54
TypedAddListener ,
50
55
TypedAddListenerAction ,
51
56
Unsubscribe ,
52
- TaskExecutor ,
57
+ ForkedTaskExecutor ,
53
58
ForkedTask ,
59
+ ForkedTaskAPI ,
54
60
AsyncTaskExecutor ,
55
61
SyncTaskExecutor ,
62
+ TaskCancelled ,
63
+ TaskRejected ,
64
+ TaskResolved ,
65
+ TaskResult ,
56
66
} from './types'
57
67
58
- function assertFunction (
59
- func : unknown ,
60
- expected : string
61
- ) : asserts func is ( ...args : unknown [ ] ) => unknown {
62
- if ( typeof func !== 'function' ) {
63
- throw new TypeError ( `${ expected } is not a function` )
64
- }
65
- }
66
-
67
68
const defaultWhen : MiddlewarePhase = 'afterReducer'
68
69
const actualMiddlewarePhases = [ 'beforeReducer' , 'afterReducer' ] as const
69
70
70
- function assertActive ( signal : AbortSignal , reason ?: string ) {
71
- if ( signal . aborted ) {
72
- throw new TaskAbortError ( reason )
73
- }
74
- }
75
-
76
- function createDelay ( signal : AbortSignal ) {
77
- return async function delay ( timeoutMs : number ) : Promise < void > {
78
- assertActive ( signal )
79
- await new Promise ( ( resolve ) => setTimeout ( resolve , timeoutMs ) )
80
- assertActive ( signal )
81
- }
82
- }
83
-
84
- function createFork ( parentAbortSignal : AbortSignal ) {
85
- return function fork < T > ( childJobExecutor : TaskExecutor < T > ) : ForkedTask < T > {
71
+ const createFork = ( parentAbortSignal : AbortSignal ) => {
72
+ return < T > ( taskExecutor : ForkedTaskExecutor < T > ) : ForkedTask < T > => {
73
+ assertFunction ( taskExecutor , 'taskExecutor' )
86
74
const childAbortController = new AbortController ( )
87
- const promise = Outcome . try ( async ( ) => {
88
- assertActive ( parentAbortSignal )
89
- const result = await Promise . resolve ( childJobExecutor ( ) )
90
- assertActive ( parentAbortSignal )
91
- assertActive ( childAbortController . signal )
75
+ const cancel = ( ) => {
76
+ childAbortController . abort ( )
77
+ }
92
78
79
+ const result = runTask < T > ( async ( ) : Promise < T > => {
80
+ validateActive ( parentAbortSignal )
81
+ validateActive ( childAbortController . signal )
82
+ const result = ( await taskExecutor ( {
83
+ pause : createPause ( childAbortController . signal ) ,
84
+ delay : createDelay ( childAbortController . signal ) ,
85
+ signal : childAbortController . signal ,
86
+ } ) ) as T
87
+ validateActive ( parentAbortSignal )
88
+ validateActive ( childAbortController . signal )
93
89
return result
94
- } )
90
+ } , cancel )
91
+
95
92
return {
96
- promise ,
97
- controller : childAbortController ,
93
+ result ,
94
+ cancel ,
98
95
}
99
96
}
100
97
}
101
98
102
- function createTakePattern < S > (
99
+ const createTakePattern = < S > (
103
100
addListener : AddListenerOverloads < Unsubscribe , S , Dispatch < AnyAction > > ,
104
101
signal : AbortSignal
105
- ) : TakePattern < S > {
102
+ ) : TakePattern < S > => {
106
103
/**
107
104
* A function that takes an ActionListenerPredicate and an optional timeout,
108
105
* and resolves when either the predicate returns `true` based on an action
109
106
* state combination or when the timeout expires.
110
107
* If the parent listener is canceled while waiting, this will throw a
111
108
* TaskAbortError.
112
109
*/
113
- async function take < P extends AnyActionListenerPredicate < S > > (
110
+ const take = async < P extends AnyActionListenerPredicate < S > > (
114
111
predicate : P ,
115
112
timeout : number | undefined
116
- ) {
117
- assertActive ( signal )
113
+ ) => {
114
+ validateActive ( signal )
118
115
119
116
// Placeholder unsubscribe function until the listener is added
120
117
let unsubscribe : Unsubscribe = ( ) => { }
121
118
122
- const signalPromise = new Promise < null > ( ( _ , reject ) => {
123
- signal . addEventListener (
124
- 'abort' ,
125
- ( ) => {
126
- reject ( new TaskAbortError ( ) )
127
- } ,
128
- { once : true }
129
- )
130
- } )
131
-
132
119
const tuplePromise = new Promise < [ AnyAction , S , S ] > ( ( resolve ) => {
133
120
// Inside the Promise, we synchronously add the listener.
134
121
unsubscribe = addListener ( {
@@ -147,7 +134,7 @@ function createTakePattern<S>(
147
134
} )
148
135
149
136
const promises : ( Promise < null > | Promise < [ AnyAction , S , S ] > ) [ ] = [
150
- signalPromise ,
137
+ promisifyAbortSignal ( signal ) ,
151
138
tuplePromise ,
152
139
]
153
140
@@ -160,7 +147,7 @@ function createTakePattern<S>(
160
147
try {
161
148
const output = await Promise . race ( promises )
162
149
163
- assertActive ( signal )
150
+ validateActive ( signal )
164
151
return output
165
152
} finally {
166
153
// Always clean up the listener
@@ -201,7 +188,7 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
201
188
listener : options . listener ,
202
189
type,
203
190
predicate,
204
- taskAbortControllerSet : new Set < AbortController > ( ) ,
191
+ pendingSet : new Set < AbortController > ( ) ,
205
192
unsubscribe : ( ) => {
206
193
throw new Error ( 'Unsubscribe not initialized' )
207
194
} ,
@@ -311,9 +298,9 @@ export function createActionListenerMiddleware<
311
298
return entry . unsubscribe
312
299
}
313
300
314
- function findListenerEntry (
301
+ const findListenerEntry = (
315
302
comparator : ( entry : ListenerEntry ) => boolean
316
- ) : ListenerEntry | undefined {
303
+ ) : ListenerEntry | undefined => {
317
304
for ( const entry of listenerMap . values ( ) ) {
318
305
if ( comparator ( entry ) ) {
319
306
return entry
@@ -364,30 +351,33 @@ export function createActionListenerMiddleware<
364
351
return true
365
352
}
366
353
367
- async function notifyListener (
354
+ const notifyListener = async (
368
355
entry : ListenerEntry < unknown , Dispatch < AnyAction > > ,
369
356
action : AnyAction ,
370
357
api : MiddlewareAPI ,
371
358
getOriginalState : ( ) => S ,
372
359
currentPhase : MiddlewarePhase
373
- ) {
360
+ ) => {
374
361
const internalTaskController = new AbortController ( )
375
362
const take = createTakePattern ( addListener , internalTaskController . signal )
376
363
const condition : ConditionFunction < S > = ( predicate , timeout ) => {
377
364
return take ( predicate , timeout ) . then ( Boolean )
378
365
}
379
366
const delay = createDelay ( internalTaskController . signal )
380
367
const fork = createFork ( internalTaskController . signal )
381
-
368
+ const pause : ( val : Promise < any > ) => Promise < any > = createPause (
369
+ internalTaskController . signal
370
+ )
382
371
try {
383
- entry . taskAbortControllerSet . add ( internalTaskController )
372
+ entry . pendingSet . add ( internalTaskController )
384
373
await Promise . resolve (
385
374
entry . listener ( action , {
386
375
...api ,
387
376
getOriginalState,
388
377
condition,
389
378
take,
390
379
delay,
380
+ pause,
391
381
currentPhase,
392
382
extra,
393
383
signal : internalTaskController . signal ,
@@ -397,12 +387,10 @@ export function createActionListenerMiddleware<
397
387
listenerMap . set ( entry . id , entry )
398
388
} ,
399
389
cancelPrevious : ( ) => {
400
- entry . taskAbortControllerSet . forEach ( ( controller , _ , set ) => {
401
- if (
402
- controller !== internalTaskController &&
403
- ! controller . signal . aborted
404
- ) {
390
+ entry . pendingSet . forEach ( ( controller , _ , set ) => {
391
+ if ( controller !== internalTaskController ) {
405
392
controller . abort ( )
393
+ set . delete ( controller )
406
394
}
407
395
} )
408
396
} ,
@@ -417,7 +405,7 @@ export function createActionListenerMiddleware<
417
405
}
418
406
} finally {
419
407
internalTaskController . abort ( ) // Notify that the task has completed
420
- entry . taskAbortControllerSet . delete ( internalTaskController )
408
+ entry . pendingSet . delete ( internalTaskController )
421
409
}
422
410
}
423
411
0 commit comments