-
Notifications
You must be signed in to change notification settings - Fork 640
/
create-action-tracking-middleware.ts
92 lines (89 loc) · 3.09 KB
/
create-action-tracking-middleware.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { IMiddlewareEvent, IMiddlewareHandler } from "../internal"
const runningActions = new Map<number, { async: boolean; call: IMiddlewareEvent; context: any }>()
export interface IActionTrackingMiddlewareHooks<T> {
filter?: (call: IMiddlewareEvent) => boolean
onStart: (call: IMiddlewareEvent) => T
onResume: (call: IMiddlewareEvent, context: T) => void
onSuspend: (call: IMiddlewareEvent, context: T) => void
onSuccess: (call: IMiddlewareEvent, context: T, result: any) => void
onFail: (call: IMiddlewareEvent, context: T, error: any) => void
}
/**
* Note: Consider migrating to `createActionTrackingMiddleware2`, it is easier to use.
*
* Convenience utility to create action based middleware that supports async processes more easily.
* All hooks are called for both synchronous and asynchronous actions. Except that either `onSuccess` or `onFail` is called
*
* The create middleware tracks the process of an action (assuming it passes the `filter`).
* `onResume` can return any value, which will be passed as second argument to any other hook. This makes it possible to keep state during a process.
*
* See the `atomic` middleware for an example
*
* @param hooks
* @returns
*/
export function createActionTrackingMiddleware<T = any>(
hooks: IActionTrackingMiddlewareHooks<T>
): IMiddlewareHandler {
return function actionTrackingMiddleware(
call: IMiddlewareEvent,
next: (actionCall: IMiddlewareEvent) => any,
abort: (value: any) => any
) {
switch (call.type) {
case "action": {
if (!hooks.filter || hooks.filter(call) === true) {
const context = hooks.onStart(call)
hooks.onResume(call, context)
runningActions.set(call.id, {
call,
context,
async: false
})
try {
const res = next(call)
hooks.onSuspend(call, context)
if (runningActions.get(call.id)!.async === false) {
runningActions.delete(call.id)
hooks.onSuccess(call, context, res)
}
return res
} catch (e) {
runningActions.delete(call.id)
hooks.onFail(call, context, e)
throw e
}
} else {
return next(call)
}
}
case "flow_spawn": {
const root = runningActions.get(call.rootId)!
root.async = true
return next(call)
}
case "flow_resume":
case "flow_resume_error": {
const root = runningActions.get(call.rootId)!
hooks.onResume(call, root.context)
try {
return next(call)
} finally {
hooks.onSuspend(call, root.context)
}
}
case "flow_throw": {
const root = runningActions.get(call.rootId)!
runningActions.delete(call.rootId)
hooks.onFail(call, root.context, call.args[0])
return next(call)
}
case "flow_return": {
const root = runningActions.get(call.rootId)!
runningActions.delete(call.rootId)
hooks.onSuccess(call, root.context, call.args[0])
return next(call)
}
}
}
}