UNPKG

4.53 kBPlain TextView Raw
1import {
2 IDerivation,
3 endBatch,
4 globalState,
5 isSpyEnabled,
6 spyReportEnd,
7 spyReportStart,
8 startBatch,
9 untrackedEnd,
10 untrackedStart,
11 isFunction,
12 allowStateReadsStart,
13 allowStateReadsEnd,
14 ACTION,
15 EMPTY_ARRAY,
16 die,
17 getDescriptor
18} from "../internal"
19
20// we don't use globalState for these in order to avoid possible issues with multiple
21// mobx versions
22let currentActionId = 0
23let nextActionId = 1
24const isFunctionNameConfigurable = getDescriptor(() => {}, "name")?.configurable ?? false
25
26// we can safely recycle this object
27const tmpNameDescriptor: PropertyDescriptor = {
28 value: "action",
29 configurable: true,
30 writable: false,
31 enumerable: false
32}
33
34export function createAction(
35 actionName: string,
36 fn: Function,
37 autoAction: boolean = false,
38 ref?: Object
39): Function {
40 if (__DEV__) {
41 if (!isFunction(fn)) die("`action` can only be invoked on functions")
42 if (typeof actionName !== "string" || !actionName)
43 die(`actions should have valid names, got: '${actionName}'`)
44 }
45 function res() {
46 return executeAction(actionName, autoAction, fn, ref || this, arguments)
47 }
48 res.isMobxAction = true
49 if (isFunctionNameConfigurable) {
50 tmpNameDescriptor.value = actionName
51 Object.defineProperty(res, "name", tmpNameDescriptor)
52 }
53 return res
54}
55
56export function executeAction(
57 actionName: string,
58 canRunAsDerivation: boolean,
59 fn: Function,
60 scope?: any,
61 args?: IArguments
62) {
63 const runInfo = _startAction(actionName, canRunAsDerivation, scope, args)
64 try {
65 return fn.apply(scope, args)
66 } catch (err) {
67 runInfo.error_ = err
68 throw err
69 } finally {
70 _endAction(runInfo)
71 }
72}
73
74export interface IActionRunInfo {
75 prevDerivation_: IDerivation | null
76 prevAllowStateChanges_: boolean
77 prevAllowStateReads_: boolean
78 notifySpy_: boolean
79 startTime_: number
80 error_?: any
81 parentActionId_: number
82 actionId_: number
83 runAsAction_?: boolean
84}
85
86export function _startAction(
87 actionName: string,
88 canRunAsDerivation: boolean, // true for autoAction
89 scope: any,
90 args?: IArguments
91): IActionRunInfo {
92 const notifySpy_ = __DEV__ && isSpyEnabled() && !!actionName
93 let startTime_: number = 0
94 if (__DEV__ && notifySpy_) {
95 startTime_ = Date.now()
96 const flattenedArgs = args ? Array.from(args) : EMPTY_ARRAY
97 spyReportStart({
98 type: ACTION,
99 name: actionName,
100 object: scope,
101 arguments: flattenedArgs
102 })
103 }
104 const prevDerivation_ = globalState.trackingDerivation
105 const runAsAction = !canRunAsDerivation || !prevDerivation_
106 startBatch()
107 let prevAllowStateChanges_ = globalState.allowStateChanges // by default preserve previous allow
108 if (runAsAction) {
109 untrackedStart()
110 prevAllowStateChanges_ = allowStateChangesStart(true)
111 }
112 const prevAllowStateReads_ = allowStateReadsStart(true)
113 const runInfo = {
114 runAsAction_: runAsAction,
115 prevDerivation_,
116 prevAllowStateChanges_,
117 prevAllowStateReads_,
118 notifySpy_,
119 startTime_,
120 actionId_: nextActionId++,
121 parentActionId_: currentActionId
122 }
123 currentActionId = runInfo.actionId_
124 return runInfo
125}
126
127export function _endAction(runInfo: IActionRunInfo) {
128 if (currentActionId !== runInfo.actionId_) {
129 die(30)
130 }
131 currentActionId = runInfo.parentActionId_
132
133 if (runInfo.error_ !== undefined) {
134 globalState.suppressReactionErrors = true
135 }
136 allowStateChangesEnd(runInfo.prevAllowStateChanges_)
137 allowStateReadsEnd(runInfo.prevAllowStateReads_)
138 endBatch()
139 if (runInfo.runAsAction_) untrackedEnd(runInfo.prevDerivation_)
140 if (__DEV__ && runInfo.notifySpy_) {
141 spyReportEnd({ time: Date.now() - runInfo.startTime_ })
142 }
143 globalState.suppressReactionErrors = false
144}
145
146export function allowStateChanges<T>(allowStateChanges: boolean, func: () => T): T {
147 const prev = allowStateChangesStart(allowStateChanges)
148 try {
149 return func()
150 } finally {
151 allowStateChangesEnd(prev)
152 }
153}
154
155export function allowStateChangesStart(allowStateChanges: boolean) {
156 const prev = globalState.allowStateChanges
157 globalState.allowStateChanges = allowStateChanges
158 return prev
159}
160
161export function allowStateChangesEnd(prev: boolean) {
162 globalState.allowStateChanges = prev
163}