Skip to content

Commit d5b0eb5

Browse files
authored
Merge pull request #1952 from FaberVitale/feat/alm-add-clear-all-listeners-method
2 parents fc5cf08 + e9230e8 commit d5b0eb5

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

packages/action-listener-middleware/src/index.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ import type {
1414
BaseActionCreator,
1515
AnyActionListenerPredicate,
1616
CreateListenerMiddlewareOptions,
17-
ConditionFunction,
18-
ListenerPredicate,
1917
TypedActionCreator,
2018
TypedAddListener,
2119
TypedAddListenerAction,
@@ -200,6 +198,20 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
200198
return entry
201199
}
202200

201+
const createClearListenerMiddleware = (
202+
listenerMap: Map<string, ListenerEntry>
203+
) => {
204+
return () => {
205+
listenerMap.forEach((entry) => {
206+
entry.pending.forEach((controller) => {
207+
controller.abort()
208+
})
209+
})
210+
211+
listenerMap.clear()
212+
}
213+
}
214+
203215
/**
204216
* Safely reports errors to the `errorHandler` provided.
205217
* Errors that occur inside `errorHandler` are notified in a new task.
@@ -240,6 +252,11 @@ export const addListenerAction = createAction(
240252
}
241253
) as TypedAddListenerAction<unknown>
242254

255+
/**
256+
* @alpha
257+
*/
258+
export const clearListenerMiddlewareAction = createAction(`${alm}/clear`)
259+
243260
/**
244261
* @alpha
245262
*/
@@ -412,6 +429,8 @@ export function createActionListenerMiddleware<
412429
}
413430
}
414431

432+
const clearListenerMiddleware = createClearListenerMiddleware(listenerMap)
433+
415434
const middleware: Middleware<
416435
{
417436
(action: Action<`${typeof alm}/add`>): Unsubscribe
@@ -430,6 +449,12 @@ export function createActionListenerMiddleware<
430449

431450
return insertEntry(entry)
432451
}
452+
453+
if (clearListenerMiddlewareAction.match(action)) {
454+
clearListenerMiddleware()
455+
return
456+
}
457+
433458
if (removeListenerAction.match(action)) {
434459
removeListener(action.payload.type, action.payload.listener)
435460
return
@@ -491,6 +516,7 @@ export function createActionListenerMiddleware<
491516
{
492517
addListener,
493518
removeListener,
519+
clear: clearListenerMiddleware,
494520
addListenerAction: addListenerAction as TypedAddListenerAction<S>,
495521
},
496522
{} as WithMiddlewareType<typeof middleware>

packages/action-listener-middleware/src/tests/listenerMiddleware.test.ts

+104
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
addListenerAction,
1515
removeListenerAction,
1616
TaskAbortError,
17+
clearListenerMiddlewareAction,
1718
} from '../index'
1819

1920
import type {
@@ -492,6 +493,109 @@ describe('createActionListenerMiddleware', () => {
492493
})
493494
})
494495

496+
describe('clear listeners', () => {
497+
test('dispatch(clearListenerAction()) cancels running listeners and removes all subscriptions', async () => {
498+
const listener1Test = deferred()
499+
let listener1Calls = 0
500+
let listener2Calls = 0
501+
let listener3Calls = 0
502+
503+
middleware.addListener({
504+
actionCreator: testAction1,
505+
async listener(_, listenerApi) {
506+
listener1Calls++
507+
listenerApi.signal.addEventListener(
508+
'abort',
509+
() => listener1Test.resolve(listener1Calls),
510+
{ once: true }
511+
)
512+
await listenerApi.condition(() => true)
513+
listener1Test.reject(new Error('unreachable: listener1Test'))
514+
},
515+
})
516+
517+
middleware.addListener({
518+
actionCreator: clearListenerMiddlewareAction,
519+
listener() {
520+
listener2Calls++
521+
},
522+
})
523+
524+
middleware.addListener({
525+
predicate: () => true,
526+
listener() {
527+
listener3Calls++
528+
},
529+
})
530+
531+
store.dispatch(testAction1('a'))
532+
store.dispatch(clearListenerMiddlewareAction())
533+
store.dispatch(testAction1('b'))
534+
expect(await listener1Test).toBe(1)
535+
expect(listener1Calls).toBe(1)
536+
expect(listener3Calls).toBe(1)
537+
expect(listener2Calls).toBe(0)
538+
})
539+
540+
test('clear() cancels running listeners and removes all subscriptions', async () => {
541+
const listener1Test = deferred()
542+
543+
let listener1Calls = 0
544+
let listener2Calls = 0
545+
546+
middleware.addListener({
547+
actionCreator: testAction1,
548+
async listener(_, listenerApi) {
549+
listener1Calls++
550+
listenerApi.signal.addEventListener(
551+
'abort',
552+
() => listener1Test.resolve(listener1Calls),
553+
{ once: true }
554+
)
555+
await listenerApi.condition(() => true)
556+
listener1Test.reject(new Error('unreachable: listener1Test'))
557+
},
558+
})
559+
560+
middleware.addListener({
561+
actionCreator: testAction2,
562+
listener() {
563+
listener2Calls++
564+
},
565+
})
566+
567+
store.dispatch(testAction1('a'))
568+
569+
middleware.clear()
570+
store.dispatch(testAction1('b'))
571+
store.dispatch(testAction2('c'))
572+
573+
expect(listener2Calls).toBe(0)
574+
expect(await listener1Test).toBe(1)
575+
})
576+
577+
test('clear() cancels all running forked tasks', async () => {
578+
const fork1Test = deferred()
579+
580+
middleware.addListener({
581+
actionCreator: testAction1,
582+
async listener(_, { fork }) {
583+
const taskResult = await fork(() => {
584+
return 3
585+
}).result
586+
fork1Test.resolve(taskResult)
587+
},
588+
})
589+
590+
store.dispatch(testAction1('a'))
591+
592+
middleware.clear()
593+
store.dispatch(testAction1('b'))
594+
595+
expect(await fork1Test).toHaveProperty('status', 'cancelled')
596+
})
597+
})
598+
495599
describe('Listener API', () => {
496600
test('Passes both getState and getOriginalState in the API', () => {
497601
const store = configureStore({

packages/action-listener-middleware/src/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ export type ActionListenerMiddleware<
218218
addListener: AddListenerOverloads<Unsubscribe, S, D>
219219
removeListener: RemoveListenerOverloads<S, D>
220220
addListenerAction: TypedAddListenerAction<S, D>
221+
/**
222+
* Unsubscribes all listeners, cancels running listeners and tasks.
223+
*/
224+
clear: () => void
221225
}
222226

223227
/**

0 commit comments

Comments
 (0)