@@ -46,6 +46,34 @@ interface ConditionFunction<State> {
46
46
47
47
type MatchFunction < T > = ( v : any ) => v is T
48
48
49
+ type TakePatternOutputWithoutTimeout <
50
+ State ,
51
+ Predicate extends AnyActionListenerPredicate < State >
52
+ > = Predicate extends MatchFunction < infer Action >
53
+ ? Promise < [ Action , State , State ] >
54
+ : Promise < [ AnyAction , State , State ] >
55
+
56
+ type TakePatternOutputWithTimeout <
57
+ State ,
58
+ Predicate extends AnyActionListenerPredicate < State >
59
+ > = Predicate extends MatchFunction < infer Action >
60
+ ? Promise < [ Action , State , State ] | null >
61
+ : Promise < [ AnyAction , State , State ] | null >
62
+
63
+ interface TakePattern < State > {
64
+ < Predicate extends AnyActionListenerPredicate < State > > (
65
+ predicate : Predicate
66
+ ) : TakePatternOutputWithoutTimeout < State , Predicate >
67
+ < Predicate extends AnyActionListenerPredicate < State > > (
68
+ predicate : Predicate ,
69
+ timeout : number
70
+ ) : TakePatternOutputWithTimeout < State , Predicate > ;
71
+ < Predicate extends AnyActionListenerPredicate < State > > (
72
+ predicate : Predicate ,
73
+ timeout ?: number | undefined
74
+ ) : Promise < [ AnyAction , State , State ] | null > ;
75
+ }
76
+
49
77
export interface HasMatchFunction < T > {
50
78
match : MatchFunction < T >
51
79
}
@@ -93,6 +121,7 @@ export interface ActionListenerMiddlewareAPI<S, D extends Dispatch<AnyAction>>
93
121
unsubscribe ( ) : void
94
122
subscribe ( ) : void
95
123
condition : ConditionFunction < S >
124
+ take : TakePattern < S >
96
125
currentPhase : MiddlewarePhase
97
126
// TODO Figure out how to pass this through the other types correctly
98
127
extra : unknown
@@ -246,6 +275,49 @@ type FallbackAddListenerOptions = (
246
275
) &
247
276
ActionListenerOptions & { listener : ActionListener < any , any , any > }
248
277
278
+ function createTakePattern < S > (
279
+ addListener : AddListenerOverloads < Unsubscribe , S , Dispatch < AnyAction > >
280
+ ) : TakePattern < S > {
281
+ async function take < P extends AnyActionListenerPredicate < S > > (
282
+ predicate : P ,
283
+ timeout : number | undefined
284
+ ) {
285
+ let unsubscribe : Unsubscribe = ( ) => { }
286
+
287
+ const tuplePromise = new Promise < [ AnyAction , S , S ] > ( ( resolve ) => {
288
+ unsubscribe = addListener ( {
289
+ predicate : predicate as any ,
290
+ listener : ( action , listenerApi ) : void => {
291
+ // One-shot listener that cleans up as soon as the predicate resolves
292
+ listenerApi . unsubscribe ( )
293
+ resolve ( [
294
+ action ,
295
+ listenerApi . getState ( ) ,
296
+ listenerApi . getOriginalState ( ) ,
297
+ ] )
298
+ } ,
299
+ } )
300
+ } )
301
+
302
+ if ( timeout === undefined ) {
303
+ return tuplePromise
304
+ }
305
+
306
+ const timedOutPromise = new Promise < null > ( ( resolve , reject ) => {
307
+ setTimeout ( ( ) => {
308
+ resolve ( null )
309
+ } , timeout )
310
+ } )
311
+
312
+ const result = await Promise . race ( [ tuplePromise , timedOutPromise ] )
313
+
314
+ unsubscribe ( )
315
+ return result
316
+ }
317
+
318
+ return take as TakePattern < S >
319
+ }
320
+
249
321
/** Accepts the possible options for creating a listener, and returns a formatted listener entry */
250
322
export const createListenerEntry : TypedCreateListenerEntry < unknown > = (
251
323
options : FallbackAddListenerOptions
@@ -413,6 +485,64 @@ export function createActionListenerMiddleware<
413
485
return entry . unsubscribe
414
486
}
415
487
488
+ function findListenerEntry (
489
+ comparator : ( entry : ListenerEntry ) = > boolean
490
+ ) : ListenerEntry | undefined {
491
+ for ( const entry of listenerMap . values ( ) ) {
492
+ if ( comparator ( entry ) ) {
493
+ return entry
494
+ }
495
+ }
496
+
497
+ return undefined
498
+ }
499
+
500
+ const addListener = ( ( options : FallbackAddListenerOptions ) => {
501
+ let entry = findListenerEntry (
502
+ ( existingEntry ) => existingEntry . listener === options . listener
503
+ )
504
+
505
+ if ( ! entry ) {
506
+ entry = createListenerEntry ( options as any )
507
+ }
508
+
509
+ return insertEntry ( entry )
510
+ } ) as TypedAddListener < S , D >
511
+
512
+ function removeListener < C extends TypedActionCreator < any > > (
513
+ actionCreator : C ,
514
+ listener : ActionListener < ReturnType < C > , S , D >
515
+ ) : boolean
516
+ function removeListener (
517
+ type : string ,
518
+ listener : ActionListener < AnyAction , S , D >
519
+ ) : boolean
520
+ function removeListener (
521
+ typeOrActionCreator : string | TypedActionCreator < any > ,
522
+ listener : ActionListener < AnyAction , S , D >
523
+ ) : boolean {
524
+ const type =
525
+ typeof typeOrActionCreator = == 'string'
526
+ ? typeOrActionCreator
527
+ : typeOrActionCreator . type
528
+
529
+ let entry = findListenerEntry (
530
+ ( entry ) => entry . type === type && entry . listener === listener
531
+ )
532
+
533
+ if ( ! entry ) {
534
+ return false
535
+ }
536
+
537
+ listenerMap . delete ( entry . id )
538
+ return true
539
+ }
540
+
541
+ const take = createTakePattern ( addListener )
542
+ const condition : ConditionFunction < S > = ( predicate , timeout ) => {
543
+ return take ( predicate , timeout ) . then ( Boolean )
544
+ }
545
+
416
546
const middleware : Middleware <
417
547
{
418
548
( action : Action < 'actionListenerMiddleware/add' > ) : Unsubscribe
@@ -469,8 +599,8 @@ export function createActionListenerMiddleware<
469
599
entry . listener ( action , {
470
600
...api ,
471
601
getOriginalState,
472
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
473
602
condition,
603
+ take,
474
604
currentPhase,
475
605
extra,
476
606
unsubscribe : entry . unsubscribe ,
@@ -490,94 +620,6 @@ export function createActionListenerMiddleware<
490
620
}
491
621
}
492
622
493
- const addListener = ( ( options : FallbackAddListenerOptions ) => {
494
- let entry = findListenerEntry (
495
- ( existingEntry ) => existingEntry . listener === options . listener
496
- )
497
-
498
- if ( ! entry ) {
499
- entry = createListenerEntry ( options as any )
500
- }
501
-
502
- return insertEntry ( entry )
503
- } ) as TypedAddListener < S , D >
504
-
505
- function removeListener < C extends TypedActionCreator < any > > (
506
- actionCreator : C ,
507
- listener : ActionListener < ReturnType < C > , S , D >
508
- ) : boolean
509
- function removeListener (
510
- type : string ,
511
- listener : ActionListener < AnyAction , S , D >
512
- ) : boolean
513
- function removeListener (
514
- typeOrActionCreator : string | TypedActionCreator < any > ,
515
- listener : ActionListener < AnyAction , S , D >
516
- ) : boolean {
517
- const type =
518
- typeof typeOrActionCreator = == 'string'
519
- ? typeOrActionCreator
520
- : typeOrActionCreator . type
521
-
522
- let entry = findListenerEntry (
523
- ( entry ) => entry . type === type && entry . listener === listener
524
- )
525
-
526
- if ( ! entry ) {
527
- return false
528
- }
529
-
530
- listenerMap . delete ( entry . id )
531
- return true
532
- }
533
-
534
- function findListenerEntry (
535
- comparator : ( entry : ListenerEntry ) => boolean
536
- ) : ListenerEntry | undefined {
537
- for ( const entry of listenerMap . values ( ) ) {
538
- if ( comparator ( entry ) ) {
539
- return entry
540
- }
541
- }
542
-
543
- return undefined
544
- }
545
-
546
- const condition : ConditionFunction < S > = async ( predicate , timeout ) = > {
547
- let unsubscribe : Unsubscribe = ( ) => { }
548
-
549
- const conditionSucceededPromise = new Promise < boolean > (
550
- ( resolve , reject ) => {
551
- unsubscribe = addListener ( {
552
- predicate,
553
- listener : ( action , listenerApi ) => {
554
- // One-shot listener that cleans up as soon as the predicate resolves
555
- listenerApi . unsubscribe ( )
556
- resolve ( true )
557
- } ,
558
- } )
559
- }
560
- )
561
-
562
- if ( timeout === undefined ) {
563
- return conditionSucceededPromise
564
- }
565
-
566
- const timedOutPromise = new Promise < boolean > ( ( resolve , reject ) => {
567
- setTimeout ( ( ) => {
568
- resolve ( false )
569
- } , timeout )
570
- } )
571
-
572
- const result = await Promise . race ( [
573
- conditionSucceededPromise ,
574
- timedOutPromise ,
575
- ] )
576
-
577
- unsubscribe ( )
578
- return result
579
- }
580
-
581
623
return Object . assign (
582
624
middleware ,
583
625
{
0 commit comments