diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/debugger/src/utils/memoizableAction.js | 75 |
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; + }; +} |