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
|
/* 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;
};
}
|