@@ -364,4 +364,135 @@ describe('Saga-style Effects Scenarios', () => {
364364
365365    expect ( canceledCheck ) . toBe ( true ) 
366366  } ) 
367+ 
368+   test ( 'long-running listener with immediate unsubscribe is cancelable' ,  async  ( )  =>  { 
369+     let  runCount  =  0 
370+     let  abortCount  =  0 
371+ 
372+     startListening ( { 
373+       actionCreator : increment , 
374+       effect : async  ( action ,  listenerApi )  =>  { 
375+         runCount ++ 
376+ 
377+         // Stop listening for this action 
378+         listenerApi . unsubscribe ( ) 
379+ 
380+         try  { 
381+           // Wait indefinitely 
382+           await  listenerApi . condition ( ( )  =>  false ) 
383+         }  catch  ( err )  { 
384+           if  ( err  instanceof  TaskAbortError )  { 
385+             abortCount ++ 
386+           } 
387+         } 
388+       } , 
389+     } ) 
390+ 
391+     // First action starts the listener, which unsubscribes 
392+     store . dispatch ( increment ( ) ) 
393+     expect ( runCount ) . toBe ( 1 ) 
394+ 
395+     // Verify that the first action unsubscribed the listener 
396+     store . dispatch ( increment ( ) ) 
397+     expect ( runCount ) . toBe ( 1 ) 
398+ 
399+     // Now call clearListeners, which should abort the running effect, even 
400+     // though the listener is no longer subscribed 
401+     listenerMiddleware . clearListeners ( ) 
402+     await  delay ( 0 ) 
403+ 
404+     expect ( abortCount ) . toBe ( 1 ) 
405+   } ) 
406+ 
407+   test ( 'long-running listener with unsubscribe race is cancelable' ,  async  ( )  =>  { 
408+     let  runCount  =  0 
409+     let  abortCount  =  0 
410+ 
411+     startListening ( { 
412+       actionCreator : increment , 
413+       effect : async  ( action ,  listenerApi )  =>  { 
414+         runCount ++ 
415+ 
416+         if  ( runCount  ===  2 )  { 
417+           // On the second run, stop listening for this action 
418+           listenerApi . unsubscribe ( ) 
419+           return 
420+         } 
421+ 
422+         try  { 
423+           // Wait indefinitely 
424+           await  listenerApi . condition ( ( )  =>  false ) 
425+         }  catch  ( err )  { 
426+           if  ( err  instanceof  TaskAbortError )  { 
427+             abortCount ++ 
428+           } 
429+         } 
430+       } , 
431+     } ) 
432+ 
433+     // First action starts the hanging effect 
434+     store . dispatch ( increment ( ) ) 
435+     expect ( runCount ) . toBe ( 1 ) 
436+ 
437+     // Second action starts the fast effect, which unsubscribes 
438+     store . dispatch ( increment ( ) ) 
439+     expect ( runCount ) . toBe ( 2 ) 
440+ 
441+     // Third action should be a noop 
442+     store . dispatch ( increment ( ) ) 
443+     expect ( runCount ) . toBe ( 2 ) 
444+ 
445+     // The hanging effect should still be hanging 
446+     expect ( abortCount ) . toBe ( 0 ) 
447+ 
448+     // Now call clearListeners, which should abort the hanging effect, even 
449+     // though the listener is no longer subscribed 
450+     listenerMiddleware . clearListeners ( ) 
451+     await  delay ( 0 ) 
452+ 
453+     expect ( abortCount ) . toBe ( 1 ) 
454+   } ) 
455+ 
456+   test ( 'long-running listener with immediate unsubscribe and forked child is cancelable' ,  async  ( )  =>  { 
457+     let  outerAborted  =  false 
458+     let  innerAborted  =  false 
459+ 
460+     startListening ( { 
461+       actionCreator : increment , 
462+       effect : async  ( action ,  listenerApi )  =>  { 
463+         // Stop listening for this action 
464+         listenerApi . unsubscribe ( ) 
465+ 
466+         const  pollingTask  =  listenerApi . fork ( async  ( forkApi )  =>  { 
467+           try  { 
468+             // Cancellation-aware indefinite pause 
469+             await  forkApi . pause ( new  Promise ( ( )  =>  { } ) ) 
470+           }  catch  ( err )  { 
471+             if  ( err  instanceof  TaskAbortError )  { 
472+               innerAborted  =  true 
473+             } 
474+           } 
475+         } ) 
476+ 
477+         try  { 
478+           // Wait indefinitely 
479+           await  listenerApi . condition ( ( )  =>  false ) 
480+           pollingTask . cancel ( ) 
481+         }  catch  ( err )  { 
482+           if  ( err  instanceof  TaskAbortError )  { 
483+             outerAborted  =  true 
484+           } 
485+         } 
486+       } , 
487+     } ) 
488+ 
489+     store . dispatch ( increment ( ) ) 
490+     await  delay ( 0 ) 
491+ 
492+     listenerMiddleware . clearListeners ( ) 
493+     await  delay ( 0 ) 
494+ 
495+     expect ( outerAborted ) . toBe ( true ) 
496+     expect ( innerAborted ) . toBe ( true ) 
497+   } ) 
367498} ) 
0 commit comments