You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Run whatever additional side-effect-y logic you want here
34
-
const { text } =action.payload
35
-
console.log('Todo added: ', text)
34
+
console.log('Todo added: ', action.payload.text)
36
35
37
-
if (text ==='Buy milk') {
38
-
// Use the listener API methods to dispatch, get state, or unsubscribe the listener
36
+
// Can cancel previous running instances
37
+
listenerApi.cancelPrevious()
38
+
39
+
// Run async logic
40
+
constdata=awaitfetchData()
41
+
42
+
// Pause until action dispatched or state changed
43
+
if (awaitlistenerApi.condition(matchSomeAction)) {
44
+
// Use the listener API methods to dispatch, get state,
45
+
// unsubscribe the listener, or cancel previous
39
46
listenerApi.dispatch(todoAdded('Buy pet food'))
40
47
listenerApi.unsubscribe()
41
48
}
@@ -228,58 +235,68 @@ The `listenerApi` object is the second argument to each listener callback. It co
228
235
229
236
-`unsubscribe: () => void`: will remove the listener from the middleware
230
237
-`subscribe: () => void`: will re-subscribe the listener if it was previously removed, or no-op if currently subscribed
231
-
-`cancelPrevious: () => void`: cancels any previously running instances of this same listener. (The cancelation will only have a meaningful effect if the previous instances are paused using one of the `job` APIs, `take`, or `condition` - see "Cancelation and Job Management" in the "Usage" section for more details)
238
+
-`cancelPrevious: () => void`: cancels any previously running instances of this same listener. (The cancelation will only have a meaningful effect if the previous instances are paused using one of the cancelation-aware APIs like `take/cancel/pause/delay` - see "Cancelation and Task Management" in the "Usage" section for more details)
239
+
-`signal: AbortSignal`: An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) whose `aborted` property will be set to `true` if the listener execution is aborted or completed.
232
240
233
241
Dynamically unsubscribing and re-subscribing this listener allows for more complex async workflows, such as avoiding duplicate running instances by calling `listenerApi.unsubscribe()` at the start of a listener, or calling `listenerApi.cancelPrevious()` to ensure that only the most recent instance is allowed to complete.
234
242
235
243
#### Conditional Workflow Execution
236
244
237
245
-`take: (predicate: ListenerPredicate, timeout?: number) => Promise<[Action, State, State] | null>`: returns a promise that will resolve when the `predicate` returns `true`. The return value is the `[action, currentState, previousState]` combination that the predicate saw as arguments. If a `timeout` is provided and expires if a `timeout` is provided and expires first. the promise resolves to `null`.
238
246
-`condition: (predicate: ListenerPredicate, timeout?: number) => Promise<boolean>`: Similar to `take`, but resolves to `true` if the predicate succeeds, and `false` if a `timeout` is provided and expires first. This allows async logic to pause and wait for some condition to occur before continuing. See "Writing Async Workflows" below for details on usage.
247
+
-`delay: (timeoutMs: number) => Promise<void>`: returns a cancelation-aware promise that resolves after the timeout, or rejects if canceled before the expiration
248
+
-`pause: (promise: Promise<T>) => Promise<T>`: accepts any promise, and returns a cancelation-aware promise that either resolves with the argument promise or rejects if canceled before the resolution
239
249
240
250
These methods provide the ability to write conditional logic based on future dispatched actions and state changes. Both also accept an optional `timeout` in milliseconds.
241
251
242
252
`take` resolves to a `[action, currentState, previousState]` tuple or `null` if it timed out, whereas `condition` resolves to `true` if it succeeded or `false` if timed out.
243
253
244
254
`take` is meant for "wait for an action and get its contents", while `condition` is meant for checks like `if (await condition(predicate))`.
245
255
246
-
Both these methods are cancelation-aware, and will throw a `JobCancelationException` if the listener instance is canceled while paused.
256
+
Both these methods are cancelation-aware, and will throw a `TaskAbortError` if the listener instance is canceled while paused.
257
+
258
+
#### Child Tasks
247
259
248
-
#### Job API
260
+
-`fork: (executor: (forkApi: ForkApi) => T | Promise<T>) => ForkedTask<T>`: Launches a "child task" that may be used to accomplish additional work. Accepts any sync or async function as its argument, and returns a `{result, cancel}` object that can be used to check the final status and return value of the child task, or cancel it while in-progress.
249
261
250
-
-`job: JobHandle`: a group of functions that allow manipulating the current running listener instance, including cancelation-aware delays, and launching "child Jobs" that can be used to run additional nested logic.
262
+
Child tasks can be launched, and waited on to collect their return values. The provided `executor` function will be called with a `forkApi` object containing `{pause, delay, signal}`, allowing it to pause or check cancelation status. It can also make use of the `listenerApi` from the listener's scope.
251
263
252
-
The job implementation is based on https://github.com/ethossoftworks/job-ts . The `JobHandle` type includes:
264
+
An example of this might be a listener that forks a child task containing an infinite loop that listens for events from a server. The parent then uses `listenerApi.condition()` to wait for a "stop" action, and cancels the child task.
`pause` and `delay` are both cancelation-aware. If the current listener is canceled, they will throw a `JobCancelationException` if the listener instance is canceled while paused.
275
+
exporttypeTaskResolved<T> = {
276
+
readonly status:'ok'
277
+
readonly value:T
278
+
}
274
279
275
-
Child jobs can be launched, and waited on to collect their return values.
280
+
exporttypeTaskRejected= {
281
+
readonly status:'rejected'
282
+
readonly error:unknown
283
+
}
276
284
277
-
Note that the jobs API relies on a functional-style async result abstraction called an `Outcome`, which wraps promise results.
285
+
exporttypeTaskCancelled= {
286
+
readonly status:'cancelled'
287
+
readonly error:TaskAbortError
288
+
}
278
289
279
-
This API will be documented more as the middleware implementation is finalized. For now, you can see the existing third-party library docs here:
@@ -289,7 +306,7 @@ This middleware lets you run additional logic when some action is dispatched, as
289
306
290
307
This middleware is not intended to handle all possible use cases. Like thunks, it provides you with a basic set of primitives (including access to `dispatch` and `getState`), and gives you freedom to write any sync or async logic you want. This is both a strength (you can do anything!) and a weakness (you can do anything, with no guard rails!).
291
308
292
-
As of v0.4.0, the middleware does include several async workflow primitives that are sufficient to write equivalents to many Redux-Saga effects operators like `takeLatest`, `takeLeading`, and `debounce`.
309
+
As of v0.5.0, the middleware does include several async workflow primitives that are sufficient to write equivalents to many Redux-Saga effects operators like `takeLatest`, `takeLeading`, and `debounce`.
293
310
294
311
### Standard Usage Patterns
295
312
@@ -401,54 +418,29 @@ test('condition method resolves promise when there is a timeout', async () => {
401
418
})
402
419
```
403
420
404
-
### Cancelation and Job Management
405
-
406
-
As of 0.4.0, the middleware now uses a `Job` abstraction to help manage cancelation of existing listener instances. The `Job` implementation is based on https://github.com/ethossoftworks/job-ts .
407
-
408
-
Each running listener instance is wrapped in a `Job` that provides cancelation awareness. A running `Job` instance has a `JobHandle` object that can be used to control it:
`listenerApi.job` exposes that `JobHandle` for the current listener instance so it can be accessed by the listener logic.
421
+
### Cancelation and Task Management
430
422
431
-
Full documentation of `JobHandle` can currently be viewed at https://github.com/ethossoftworks/job-ts/blob/main/docs/api.md . Note that this API also uses a custom functional-style wrapper around async results called an `Outcome`: https://github.com/ethossoftworks/outcome-ts.
423
+
As of 0.5.0, the middleware now supports cancelation of running listener instances, `take/condition`/pause/delay`functions, and"child tasks", withanimplementationbasedon [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
432
424
433
-
The `listenerApi.job.pause/delay()` functions provide a cancelation-aware way to have the current listener sleep. `pause()` accepts a promise, while `delay` accepts a timeout value. If the listener is canceled while waiting, a `JobCancelationException` will be thrown.
This can also be used to launch "child jobs" that can do additional work. These can be waited on to collect their results. An example of this might look like:
// Complete the child by returning an Outcome-wrapped value
446
-
returnOutcome.ok(42)
436
+
await forkApi.delay(5)
437
+
// Complete the child by returning an Ovalue
438
+
return 42
447
439
})
448
440
449
-
const result =awaitchildJobPromise
441
+
const result = await task.result
450
442
// Unwrap the child result in the listener
451
-
if (result.isOk()) {
443
+
if (result.status === 'ok') {
452
444
console.log('Child succeeded: ', result.value)
453
445
}
454
446
},
@@ -457,7 +449,7 @@ middleware.addListener({
457
449
458
450
### ComplexAsyncWorkflows
459
451
460
-
The provided async workflow primitives (`cancelPrevious`, `unsuscribe`, `subscribe`, `take`, `condition`, `job.pause`, `job.delay`) can be used to implement many of the more complex async workflow capabilities found in the Redux-Saga library. This includes effects such as `throttle`, `debounce`, `takeLatest`, `takeLeading`, and `fork/join`. Some examples:
0 commit comments