Open
Description
Description
Current implementation of listernerApi.fork has an implicit contract:
1. The output forked task is scheduled **synchronously** in a microtask.
2. The output forked task will **always** run if it is not cancelled **synchronously**.
The second clause can be problematic in certain situations because It is possible to cancel a forked task that we're going to execute anyway:
Examples
1. using process.nextTick
listenerMiddleware.startListening({
actionCreator: increment,
async effect(action, listenerApi) {
const task = listenerApi.fork(async (forkApi) => { console.log(2); })
globalThis?.process?.nextTick(() => { task.cancel(); console.log(1); }) // nextTick (node only) runs before the microtask queue
},
})
2. using task.cancel in a scheduled microtask
listenerMiddleware.startListening({
actionCreator: increment,
async effect(action, listenerApi) {
let task: ForkedTask<void>;
globalThis?.queueMicrotask(() => { task?.cancel(); console.log(1); })
task = listenerApi.fork(async (forkApi) => { console.log(2); })
},
})
While these examples might seem contrived, we do not generally use node-only primitives in universal code, it highlights an issue with the current implementation that might cause problems if the user does not use forkApi.signal
or the other forkApi methods.
Suggested changes
Change the listernerApi.fork contract to:
1. The output forked task is scheduled **synchronously** in a microtask.
- 2. The output forked task will **always** run if it is not cancelled **synchronously**.
+ 2. The output forked task will **always** run if it is not cancelled before the scheduled execution.