@@ -29,13 +29,18 @@ import type {
2929 WithMiddlewareType ,
3030 TakePattern ,
3131 ListenerErrorInfo ,
32- TaskExecutor ,
32+ ForkedTaskExecutor ,
3333 ForkedTask ,
3434} from './types'
35-
35+ import { assertFunction } from './utils'
3636import { 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'
3944export { TaskAbortError } from './exceptions'
4045export type {
4146 ActionListener ,
@@ -49,86 +54,68 @@ export type {
4954 TypedAddListener ,
5055 TypedAddListenerAction ,
5156 Unsubscribe ,
52- TaskExecutor ,
57+ ForkedTaskExecutor ,
5358 ForkedTask ,
59+ ForkedTaskAPI ,
5460 AsyncTaskExecutor ,
5561 SyncTaskExecutor ,
62+ TaskCancelled ,
63+ TaskRejected ,
64+ TaskResolved ,
65+ TaskResult ,
5666} from './types'
5767
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-
6768const defaultWhen : MiddlewarePhase = 'afterReducer'
6869const actualMiddlewarePhases = [ 'beforeReducer' , 'afterReducer' ] as const
6970
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' )
8674 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+ }
9278
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 )
9389 return result
94- } )
90+ } , cancel )
91+
9592 return {
96- promise ,
97- controller : childAbortController ,
93+ result ,
94+ cancel ,
9895 }
9996 }
10097}
10198
102- function createTakePattern < S > (
99+ const createTakePattern = < S > (
103100 addListener : AddListenerOverloads < Unsubscribe , S , Dispatch < AnyAction > > ,
104101 signal : AbortSignal
105- ) : TakePattern < S > {
102+ ) : TakePattern < S > => {
106103 /**
107104 * A function that takes an ActionListenerPredicate and an optional timeout,
108105 * and resolves when either the predicate returns `true` based on an action
109106 * state combination or when the timeout expires.
110107 * If the parent listener is canceled while waiting, this will throw a
111108 * TaskAbortError.
112109 */
113- async function take < P extends AnyActionListenerPredicate < S > > (
110+ const take = async < P extends AnyActionListenerPredicate < S > > (
114111 predicate : P ,
115112 timeout : number | undefined
116- ) {
117- assertActive ( signal )
113+ ) => {
114+ validateActive ( signal )
118115
119116 // Placeholder unsubscribe function until the listener is added
120117 let unsubscribe : Unsubscribe = ( ) => { }
121118
122- const signalPromise = new Promise < null > ( ( _ , reject ) => {
123- signal . addEventListener (
124- 'abort' ,
125- ( ) => {
126- reject ( new TaskAbortError ( ) )
127- } ,
128- { once : true }
129- )
130- } )
131-
132119 const tuplePromise = new Promise < [ AnyAction , S , S ] > ( ( resolve ) => {
133120 // Inside the Promise, we synchronously add the listener.
134121 unsubscribe = addListener ( {
@@ -147,7 +134,7 @@ function createTakePattern<S>(
147134 } )
148135
149136 const promises : ( Promise < null > | Promise < [ AnyAction , S , S ] > ) [ ] = [
150- signalPromise ,
137+ promisifyAbortSignal ( signal ) ,
151138 tuplePromise ,
152139 ]
153140
@@ -160,7 +147,7 @@ function createTakePattern<S>(
160147 try {
161148 const output = await Promise . race ( promises )
162149
163- assertActive ( signal )
150+ validateActive ( signal )
164151 return output
165152 } finally {
166153 // Always clean up the listener
@@ -201,7 +188,7 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
201188 listener : options . listener ,
202189 type,
203190 predicate,
204- taskAbortControllerSet : new Set < AbortController > ( ) ,
191+ pendingSet : new Set < AbortController > ( ) ,
205192 unsubscribe : ( ) => {
206193 throw new Error ( 'Unsubscribe not initialized' )
207194 } ,
@@ -311,9 +298,9 @@ export function createActionListenerMiddleware<
311298 return entry . unsubscribe
312299 }
313300
314- function findListenerEntry (
301+ const findListenerEntry = (
315302 comparator : ( entry : ListenerEntry ) => boolean
316- ) : ListenerEntry | undefined {
303+ ) : ListenerEntry | undefined => {
317304 for ( const entry of listenerMap . values ( ) ) {
318305 if ( comparator ( entry ) ) {
319306 return entry
@@ -364,30 +351,33 @@ export function createActionListenerMiddleware<
364351 return true
365352 }
366353
367- async function notifyListener (
354+ const notifyListener = async (
368355 entry : ListenerEntry < unknown , Dispatch < AnyAction > > ,
369356 action : AnyAction ,
370357 api : MiddlewareAPI ,
371358 getOriginalState : ( ) => S ,
372359 currentPhase : MiddlewarePhase
373- ) {
360+ ) => {
374361 const internalTaskController = new AbortController ( )
375362 const take = createTakePattern ( addListener , internalTaskController . signal )
376363 const condition : ConditionFunction < S > = ( predicate , timeout ) => {
377364 return take ( predicate , timeout ) . then ( Boolean )
378365 }
379366 const delay = createDelay ( internalTaskController . signal )
380367 const fork = createFork ( internalTaskController . signal )
381-
368+ const pause : ( val : Promise < any > ) => Promise < any > = createPause (
369+ internalTaskController . signal
370+ )
382371 try {
383- entry . taskAbortControllerSet . add ( internalTaskController )
372+ entry . pendingSet . add ( internalTaskController )
384373 await Promise . resolve (
385374 entry . listener ( action , {
386375 ...api ,
387376 getOriginalState,
388377 condition,
389378 take,
390379 delay,
380+ pause,
391381 currentPhase,
392382 extra,
393383 signal : internalTaskController . signal ,
@@ -397,12 +387,10 @@ export function createActionListenerMiddleware<
397387 listenerMap . set ( entry . id , entry )
398388 } ,
399389 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 ) {
405392 controller . abort ( )
393+ set . delete ( controller )
406394 }
407395 } )
408396 } ,
@@ -417,7 +405,7 @@ export function createActionListenerMiddleware<
417405 }
418406 } finally {
419407 internalTaskController . abort ( ) // Notify that the task has completed
420- entry . taskAbortControllerSet . delete ( internalTaskController )
408+ entry . pendingSet . delete ( internalTaskController )
421409 }
422410 }
423411
0 commit comments