Skip to content

Commit 5f41cd6

Browse files
committed
WIP Sketch out ideas for Job-based condition handling
1 parent d16709c commit 5f41cd6

File tree

3 files changed

+53
-13
lines changed

3 files changed

+53
-13
lines changed

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,20 @@ const defaultWhen: MiddlewarePhase = 'afterReducer'
6666
const actualMiddlewarePhases = ['beforeReducer', 'afterReducer'] as const
6767

6868
function createTakePattern<S>(
69-
addListener: AddListenerOverloads<Unsubscribe, S, Dispatch<AnyAction>>
69+
addListener: AddListenerOverloads<Unsubscribe, S, Dispatch<AnyAction>>,
70+
parentJob?: Job<any>
7071
): TakePattern<S> {
7172
async function take<P extends AnyActionListenerPredicate<S>>(
7273
predicate: P,
7374
timeout: number | undefined
7475
) {
7576
let unsubscribe: Unsubscribe = () => {}
77+
let job: JobHandle
78+
79+
// TODO Need to figure out how to propagate cancelations of the job that
80+
// is locked inside of the middleware back up into here, so that a canceled
81+
// listener waiting on a condition has that condition throw instead.
82+
// Maybe rewrite these around use of `Job.pause()` instead?
7683

7784
const tuplePromise = new Promise<[AnyAction, S, S]>((resolve) => {
7885
unsubscribe = addListener({
@@ -86,20 +93,22 @@ function createTakePattern<S>(
8693
listenerApi.getOriginalState(),
8794
])
8895
},
96+
parentJob,
8997
})
9098
})
9199

92-
if (timeout === undefined) {
93-
return tuplePromise
94-
}
100+
let promises: Promise<unknown>[] = [tuplePromise]
95101

96-
const timedOutPromise = new Promise<null>((resolve, reject) => {
97-
setTimeout(() => {
98-
resolve(null)
99-
}, timeout)
100-
})
102+
if (timeout !== undefined) {
103+
const timedOutPromise = new Promise<null>((resolve, reject) => {
104+
setTimeout(() => {
105+
resolve(null)
106+
}, timeout)
107+
})
108+
promises = [tuplePromise, timedOutPromise]
109+
}
101110

102-
const result = await Promise.race([tuplePromise, timedOutPromise])
111+
const result = await Promise.race(promises)
103112

104113
unsubscribe()
105114
return result
@@ -386,7 +395,10 @@ export function createActionListenerMiddleware<
386395
},
387396
})
388397
)
389-
if (result.isError()) {
398+
if (
399+
result.isError() &&
400+
!(result.error instanceof JobCancellationException)
401+
) {
390402
safelyNotifyError(onError, result.error, {
391403
raisedBy: 'listener',
392404
phase: currentPhase,

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,16 @@ describe('createActionListenerMiddleware', () => {
9393
increment(state) {
9494
state.value += 1
9595
},
96+
decrement(state) {
97+
state.value -= 1
98+
},
9699
// Use the PayloadAction type to declare the contents of `action.payload`
97100
incrementByAmount: (state, action: PayloadAction<number>) => {
98101
state.value += action.payload
99102
},
100103
},
101104
})
102-
const { increment, incrementByAmount } = counterSlice.actions
105+
const { increment, decrement, incrementByAmount } = counterSlice.actions
103106

104107
function delay(ms: number) {
105108
return new Promise((resolve) => setTimeout(resolve, ms))
@@ -846,6 +849,30 @@ describe('createActionListenerMiddleware', () => {
846849
})
847850
})
848851

852+
describe('Job API', () => {
853+
test.skip('Allows canceling previous jobs', () => {
854+
let jobsStarted = 0
855+
856+
middleware.addListener({
857+
actionCreator: increment,
858+
listener: async (action, listenerApi) => {
859+
jobsStarted++
860+
861+
if (jobsStarted < 3) {
862+
await listenerApi.condition(decrement.match)
863+
// Cancelation _should_ cause `condition()` to throw so we never
864+
// end up hitting this next line
865+
console.log('Continuing after decrement')
866+
} else {
867+
listenerApi.cancelPrevious()
868+
}
869+
},
870+
})
871+
872+
// TODO Write the rest of this test
873+
})
874+
})
875+
849876
describe('Type tests', () => {
850877
const middleware = createActionListenerMiddleware()
851878
const store = configureStore({

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
ThunkDispatch,
99
} from '@reduxjs/toolkit'
1010

11-
import type { JobHandle } from './job'
11+
import type { JobHandle, Job } from './job'
1212

1313
/**
1414
* Types copied from RTK
@@ -100,6 +100,7 @@ export interface ActionListenerOptions {
100100
* Defaults to 'before'.
101101
*/
102102
when?: When
103+
parentJob?: JobHandle
103104
}
104105

105106
export interface CreateListenerMiddlewareOptions<ExtraArgument = unknown> {

0 commit comments

Comments
 (0)