Skip to content

Commit f29cfc9

Browse files
committed
feat: implement multiGet function
1 parent 85f2356 commit f29cfc9

File tree

1 file changed

+53
-21
lines changed

1 file changed

+53
-21
lines changed

lib/Onyx.js

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,65 @@ deferredInit.promise = new Promise((res, rej) => {
3636
});
3737

3838
/**
39-
* Get some data from the store
39+
* Get multiple keys in a single batch
4040
*
41-
* @param {string} key
42-
* @returns {Promise<*>}
41+
* @param {string[]} keys
42+
* @returns {Promise<Record<string, *>>} - an object of key -> value mapping
4343
*/
44-
function get(key) {
45-
// When we already have the value in cache - resolve right away
46-
if (cache.hasCacheForKey(key)) {
47-
return Promise.resolve(cache.getValue(key));
48-
}
44+
function multiGet(keys) {
45+
const [fromCache, hardReads] = _.partition(
46+
keys,
47+
key => cache.hasCacheForKey(key) || cache.hasPendingTask(`get:${key}`)
48+
);
49+
50+
// Init a list of tasks containing any pending reads from storage
51+
const tasks = fromCache
52+
.filter(key => cache.hasPendingTask(`get:${key}`))
53+
.map(key => Promise.resolve(cache.getTaskPromise(`get:${key}`)));
54+
55+
// Append a task for any remaining "hard reads" the current list
56+
if (hardReads.length > 0) {
57+
const readKeysFromStorage = AsyncStorage.multiGet(hardReads)
58+
.then(pairs => pairs.forEach(([key, val]) => {
59+
const parsed = val && JSON.parse(val);
60+
cache.set(key, parsed || null);
61+
}));
4962

50-
const taskName = `get:${key}`;
63+
tasks.push(readKeysFromStorage);
5164

52-
// When a value retrieving task for this key is still running hook to it
53-
if (cache.hasPendingTask(taskName)) {
54-
return cache.getTaskPromise(taskName);
65+
/* Capture a resolver for potential multiGet calls that happen while we retrieve keys
66+
* example one one call retrieves keys A,B,C another call retrieves B,C,D
67+
* we don't need to start a new read for B,C we'll hook them from the first call
68+
*/
69+
hardReads.forEach((key) => {
70+
cache.captureTask(`get:${key}`, readKeysFromStorage.then(() => cache.getValue(key)));
71+
});
5572
}
5673

57-
// Otherwise retrieve the value from storage and capture a promise to aid concurrent usages
58-
const promise = AsyncStorage.getItem(key)
59-
.then((val) => {
60-
const parsed = val && JSON.parse(val);
61-
cache.set(key, parsed);
62-
return parsed;
63-
})
64-
.catch(err => logInfo(`Unable to get item from persistent storage. Key: ${key} Error: ${err}`));
74+
// Return a grouped result
75+
return Promise.all(tasks)
76+
.then(() => {
77+
/* At this point we have everything in cache
78+
* Instead of searching for the value inside the result, we can retrieved it by key from cache */
79+
const result = _.chain(keys)
80+
.map(key => [key, cache.getValue(key)])
81+
.object()
82+
.value();
6583

66-
return cache.captureTask(taskName, promise);
84+
return result;
85+
});
86+
}
87+
88+
/**
89+
* Get some data from the store
90+
*
91+
* @param {string} key
92+
* @returns {Promise<*>}
93+
*/
94+
function get(key) {
95+
/* We use multiGet as it will batch multiple calls and flush them on the next iteration of
96+
* the event loop. AsyncStorage uses `setImmediate` to do this internally */
97+
return multiGet([key]).then(result => result[key]);
6798
}
6899

69100
/**
@@ -781,6 +812,7 @@ function applyDecorators() {
781812
// Re-assign with decorated functions
782813
/* eslint-disable no-func-assign */
783814
get = decorate.decorateWithMetrics(get, 'Onyx:get');
815+
multiGet = decorate.decorateWithMetrics(multiGet, 'Onyx:multiGet');
784816
set = decorate.decorateWithMetrics(set, 'Onyx:set');
785817
multiSet = decorate.decorateWithMetrics(multiSet, 'Onyx:multiSet');
786818
clear = decorate.decorateWithMetrics(clear, 'Onyx:clear');

0 commit comments

Comments
 (0)