summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/utils/memoizableAction.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/client/debugger/src/utils/memoizableAction.js75
1 files changed, 75 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/utils/memoizableAction.js b/devtools/client/debugger/src/utils/memoizableAction.js
new file mode 100644
index 0000000000..0f465177e2
--- /dev/null
+++ b/devtools/client/debugger/src/utils/memoizableAction.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+import { asSettled } from "./async-value";
+import { validateContext } from "./context";
+
+/*
+ * memoizableActon is a utility for actions that should only be performed
+ * once per key. It is useful for loading sources, parsing symbols ...
+ *
+ * @getValue - gets the result from the redux store
+ * @createKey - creates a key for the requests map
+ * @action - kicks off the async work for the action
+ *
+ *
+ * For Example
+ *
+ * export const setItem = memoizeableAction(
+ * "setItem",
+ * {
+ * hasValue: ({ a }, { getState }) => hasItem(getState(), a),
+ * getValue: ({ a }, { getState }) => getItem(getState(), a),
+ * createKey: ({ a }) => a,
+ * action: ({ a }, thunkArgs) => doSetItem(a, thunkArgs)
+ * }
+ * );
+ *
+ */
+export function memoizeableAction(name, { getValue, createKey, action }) {
+ const requests = new Map();
+ return args => async thunkArgs => {
+ let result = asSettled(getValue(args, thunkArgs));
+ if (!result) {
+ const key = createKey(args, thunkArgs);
+ if (!requests.has(key)) {
+ requests.set(
+ key,
+ (async () => {
+ try {
+ await action(args, thunkArgs);
+ } catch (e) {
+ console.warn(`Action ${name} had an exception:`, e);
+ } finally {
+ requests.delete(key);
+ }
+ })()
+ );
+ }
+
+ await requests.get(key);
+
+ if (args.cx) {
+ validateContext(thunkArgs.getState(), args.cx);
+ }
+
+ result = asSettled(getValue(args, thunkArgs));
+ if (!result) {
+ // Returning null here is not ideal. This means that the action
+ // resolved but 'getValue' didn't return a loaded value, for instance
+ // if the data the action was meant to store was deleted. In a perfect
+ // world we'd throw a ContextError here or handle cancellation somehow.
+ // Throwing will also allow us to change the return type on the action
+ // to always return a promise for the getValue AsyncValue type, but
+ // for now we have to add an additional '| null' for this case.
+ return null;
+ }
+ }
+
+ if (result.state === "rejected") {
+ throw result.value;
+ }
+ return result.value;
+ };
+}