Skip to content

Commit 4830aff

Browse files
committed
feat(alm): add alm.clear() method
Context: #1648 (comment)
1 parent a7af01d commit 4830aff

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

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

Lines changed: 13 additions & 2 deletions
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,18 @@ export const createListenerEntry: TypedCreateListenerEntry<unknown> = (
200198
return entry
201199
}
202200

201+
const createClearAllListeners = (listenerMap: Map<string, ListenerEntry>) => {
202+
return () => {
203+
listenerMap.forEach((entry) => {
204+
entry.pending.forEach((controller) => {
205+
controller.abort()
206+
})
207+
})
208+
209+
listenerMap.clear()
210+
}
211+
}
212+
203213
/**
204214
* Safely reports errors to the `errorHandler` provided.
205215
* Errors that occur inside `errorHandler` are notified in a new task.
@@ -491,6 +501,7 @@ export function createActionListenerMiddleware<
491501
{
492502
addListener,
493503
removeListener,
504+
clear: createClearAllListeners(listenerMap),
494505
addListenerAction: addListenerAction as TypedAddListenerAction<S>,
495506
},
496507
{} as WithMiddlewareType<typeof middleware>

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,64 @@ describe('createActionListenerMiddleware', () => {
492492
})
493493
})
494494

495+
test('clear() cancels running listeners and removes all subscriptions', async () => {
496+
const listener1Test = deferred()
497+
498+
let listener1Calls = 0
499+
let listener2Calls = 0
500+
501+
middleware.addListener({
502+
actionCreator: testAction1,
503+
async listener(_, listenerApi) {
504+
listener1Calls++
505+
listenerApi.signal.addEventListener(
506+
'abort',
507+
() => listener1Test.resolve(listener1Calls),
508+
{ once: true }
509+
)
510+
await listenerApi.condition(() => true)
511+
listener1Test.reject(new Error('unreachable: listener1Test'))
512+
},
513+
})
514+
515+
middleware.addListener({
516+
actionCreator: testAction2,
517+
listener() {
518+
listener2Calls++
519+
},
520+
})
521+
522+
store.dispatch(testAction1('a'))
523+
524+
middleware.clear()
525+
store.dispatch(testAction1('b'))
526+
store.dispatch(testAction2('c'))
527+
528+
expect(listener2Calls).toBe(0)
529+
expect(await listener1Test).toBe(1)
530+
})
531+
532+
test('clear() cancels all running forked tasks', async () => {
533+
const fork1Test = deferred()
534+
535+
middleware.addListener({
536+
actionCreator: testAction1,
537+
async listener(_, { fork }) {
538+
const taskResult = await fork(() => {
539+
return 3
540+
}).result
541+
fork1Test.resolve(taskResult)
542+
},
543+
})
544+
545+
store.dispatch(testAction1('a'))
546+
547+
middleware.clear()
548+
store.dispatch(testAction1('b'))
549+
550+
expect(await fork1Test).toHaveProperty('status', 'cancelled')
551+
})
552+
495553
describe('Listener API', () => {
496554
test('Passes both getState and getOriginalState in the API', () => {
497555
const store = configureStore({

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

Lines changed: 4 additions & 0 deletions
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+
* Removes all subscribed listeners, cancels running listeners and tasks.
223+
*/
224+
clear: () => void
221225
}
222226

223227
/**

0 commit comments

Comments
 (0)