@@ -36,34 +36,65 @@ deferredInit.promise = new Promise((res, rej) => {
36
36
} ) ;
37
37
38
38
/**
39
- * Get some data from the store
39
+ * Get multiple keys in a single batch
40
40
*
41
- * @param {string } key
42
- * @returns {Promise<*> }
41
+ * @param {string[] } keys
42
+ * @returns {Promise<Record<string, *>> } - an object of key -> value mapping
43
43
*/
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
+ } ) ) ;
49
62
50
- const taskName = `get: ${ key } ` ;
63
+ tasks . push ( readKeysFromStorage ) ;
51
64
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
+ } ) ;
55
72
}
56
73
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 ( ) ;
65
83
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 ] ) ;
67
98
}
68
99
69
100
/**
@@ -781,6 +812,7 @@ function applyDecorators() {
781
812
// Re-assign with decorated functions
782
813
/* eslint-disable no-func-assign */
783
814
get = decorate . decorateWithMetrics ( get , 'Onyx:get' ) ;
815
+ multiGet = decorate . decorateWithMetrics ( multiGet , 'Onyx:multiGet' ) ;
784
816
set = decorate . decorateWithMetrics ( set , 'Onyx:set' ) ;
785
817
multiSet = decorate . decorateWithMetrics ( multiSet , 'Onyx:multiSet' ) ;
786
818
clear = decorate . decorateWithMetrics ( clear , 'Onyx:clear' ) ;
0 commit comments