1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {
9
+ checkValidArgs ,
10
+ checkValidInput ,
11
+ convertError ,
12
+ convertErrors ,
13
+ } from "./helpers" ;
14
+ import RCTAsyncStorage from "./RCTAsyncStorage" ;
15
+ import type {
16
+ AsyncStorageStatic ,
17
+ ErrorLike ,
18
+ KeyValuePair ,
19
+ MultiRequest ,
20
+ } from "./types" ;
21
+
22
+ if ( ! RCTAsyncStorage ) {
23
+ throw new Error ( `[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
24
+
25
+ To fix this issue try these steps:
26
+
27
+ • Uninstall, rebuild and restart the app.
28
+
29
+ • Run the packager with \`--reset-cache\` flag.
30
+
31
+ • If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory, then rebuild and re-run the app.
32
+
33
+ • Make sure your project's \`package.json\` depends on \`@react-native-async-storage/async-storage\`, even if you only depend on it indirectly through other dependencies. CLI only autolinks native modules found in your \`package.json\`.
34
+
35
+ • If this happens while testing with Jest, check out how to integrate AsyncStorage here: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
36
+
37
+ If none of these fix the issue, please open an issue on the GitHub repository: https://github.com/react-native-async-storage/async-storage/issues
38
+ ` ) ;
39
+ }
40
+
41
+ /**
42
+ * `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
43
+ * storage system that is global to the app. It should be used instead of
44
+ * LocalStorage.
45
+ *
46
+ * See https://react-native-async-storage.github.io/async-storage/docs/api
47
+ */
48
+ const AsyncStorage = ( ( ) : AsyncStorageStatic => {
49
+ let _getRequests : MultiRequest [ ] = [ ] ;
50
+ let _getKeys : string [ ] = [ ] ;
51
+ let _immediate : ReturnType < typeof setImmediate > | null = null ;
52
+
53
+ return {
54
+ /**
55
+ * Fetches an item for a `key` and invokes a callback upon completion.
56
+ *
57
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
58
+ */
59
+ getItem : ( key , callback ) => {
60
+ return new Promise ( ( resolve , reject ) => {
61
+ checkValidInput ( key ) ;
62
+ RCTAsyncStorage . multiGet (
63
+ [ key ] ,
64
+ ( errors ?: ErrorLike [ ] , result ?: string [ ] [ ] ) => {
65
+ // Unpack result to get value from [[key,value]]
66
+ const value = result ?. [ 0 ] ?. [ 1 ] ? result [ 0 ] [ 1 ] : null ;
67
+ const errs = convertErrors ( errors ) ;
68
+ callback ?.( errs ?. [ 0 ] , value ) ;
69
+ if ( errs ) {
70
+ reject ( errs [ 0 ] ) ;
71
+ } else {
72
+ resolve ( value ) ;
73
+ }
74
+ }
75
+ ) ;
76
+ } ) ;
77
+ } ,
78
+
79
+ /**
80
+ * Sets the value for a `key` and invokes a callback upon completion.
81
+ *
82
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
83
+ */
84
+ setItem : ( key , value , callback ) => {
85
+ return new Promise ( ( resolve , reject ) => {
86
+ checkValidInput ( key , value ) ;
87
+ RCTAsyncStorage . multiSet ( [ [ key , value ] ] , ( errors ?: ErrorLike [ ] ) => {
88
+ const errs = convertErrors ( errors ) ;
89
+ callback ?.( errs ?. [ 0 ] ) ;
90
+ if ( errs ) {
91
+ reject ( errs [ 0 ] ) ;
92
+ } else {
93
+ resolve ( ) ;
94
+ }
95
+ } ) ;
96
+ } ) ;
97
+ } ,
98
+
99
+ /**
100
+ * Removes an item for a `key` and invokes a callback upon completion.
101
+ *
102
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
103
+ */
104
+ removeItem : ( key , callback ) => {
105
+ return new Promise ( ( resolve , reject ) => {
106
+ checkValidInput ( key ) ;
107
+ RCTAsyncStorage . multiRemove ( [ key ] , ( errors ?: ErrorLike [ ] ) => {
108
+ const errs = convertErrors ( errors ) ;
109
+ callback ?.( errs ?. [ 0 ] ) ;
110
+ if ( errs ) {
111
+ reject ( errs [ 0 ] ) ;
112
+ } else {
113
+ resolve ( ) ;
114
+ }
115
+ } ) ;
116
+ } ) ;
117
+ } ,
118
+
119
+ /**
120
+ * Merges an existing `key` value with an input value, assuming both values
121
+ * are stringified JSON.
122
+ *
123
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
124
+ */
125
+ mergeItem : ( key , value , callback ) => {
126
+ return new Promise ( ( resolve , reject ) => {
127
+ checkValidInput ( key , value ) ;
128
+ RCTAsyncStorage . multiMerge ( [ [ key , value ] ] , ( errors ?: ErrorLike [ ] ) => {
129
+ const errs = convertErrors ( errors ) ;
130
+ callback ?.( errs ?. [ 0 ] ) ;
131
+ if ( errs ) {
132
+ reject ( errs [ 0 ] ) ;
133
+ } else {
134
+ resolve ( ) ;
135
+ }
136
+ } ) ;
137
+ } ) ;
138
+ } ,
139
+
140
+ /**
141
+ * Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
142
+ * don't want to call this; use `removeItem` or `multiRemove` to clear only
143
+ * your app's keys.
144
+ *
145
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#clear
146
+ */
147
+ clear : ( callback ) => {
148
+ return new Promise ( ( resolve , reject ) => {
149
+ RCTAsyncStorage . clear ( ( error ?: ErrorLike ) => {
150
+ const err = convertError ( error ) ;
151
+ callback ?.( err ) ;
152
+ if ( err ) {
153
+ reject ( err ) ;
154
+ } else {
155
+ resolve ( ) ;
156
+ }
157
+ } ) ;
158
+ } ) ;
159
+ } ,
160
+
161
+ /**
162
+ * Gets *all* keys known to your app; for all callers, libraries, etc.
163
+ *
164
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
165
+ */
166
+ getAllKeys : ( callback ) => {
167
+ return new Promise ( ( resolve , reject ) => {
168
+ RCTAsyncStorage . getAllKeys ( ( error ?: ErrorLike , keys ?: string [ ] ) => {
169
+ const err = convertError ( error ) ;
170
+ callback ?.( err , keys ) ;
171
+ if ( keys ) {
172
+ resolve ( keys ) ;
173
+ } else {
174
+ reject ( err ) ;
175
+ }
176
+ } ) ;
177
+ } ) ;
178
+ } ,
179
+
180
+ /**
181
+ * The following batched functions are useful for executing a lot of
182
+ * operations at once, allowing for native optimizations and provide the
183
+ * convenience of a single callback after all operations are complete.
184
+ *
185
+ * These functions return arrays of errors, potentially one for every key.
186
+ * For key-specific errors, the Error object will have a key property to
187
+ * indicate which key caused the error.
188
+ */
189
+
190
+ /**
191
+ * Flushes any pending requests using a single batch call to get the data.
192
+ *
193
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
194
+ * */
195
+ flushGetRequests : ( ) => {
196
+ const getRequests = _getRequests ;
197
+ const getKeys = _getKeys ;
198
+
199
+ _getRequests = [ ] ;
200
+ _getKeys = [ ] ;
201
+
202
+ RCTAsyncStorage . multiGet (
203
+ getKeys ,
204
+ ( errors ?: ErrorLike [ ] , result ?: string [ ] [ ] ) => {
205
+ // Even though the runtime complexity of this is theoretically worse vs if we used a map,
206
+ // it's much, much faster in practice for the data sets we deal with (we avoid
207
+ // allocating result pair arrays). This was heavily benchmarked.
208
+ //
209
+ // Is there a way to avoid using the map but fix the bug in this breaking test?
210
+ // https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
211
+ const map : Record < string , string > = { } ;
212
+ result ?. forEach ( ( [ key , value ] ) => {
213
+ map [ key ] = value ;
214
+ return value ;
215
+ } ) ;
216
+ const reqLength = getRequests . length ;
217
+
218
+ /**
219
+ * As mentioned few lines above, this method could be called with the array of potential error,
220
+ * in case of anything goes wrong. The problem is, if any of the batched calls fails
221
+ * the rest of them would fail too, but the error would be consumed by just one. The rest
222
+ * would simply return `undefined` as their result, rendering false negatives.
223
+ *
224
+ * In order to avoid this situation, in case of any call failing,
225
+ * the rest of them will be rejected as well (with the same error).
226
+ */
227
+ const errorList = convertErrors ( errors ) ;
228
+ const error = errorList ?. length ? errorList [ 0 ] : null ;
229
+
230
+ for ( let i = 0 ; i < reqLength ; i ++ ) {
231
+ const request = getRequests [ i ] ;
232
+ if ( error ) {
233
+ request . callback ?.( errorList ) ;
234
+ request . reject ?.( error ) ;
235
+ continue ;
236
+ }
237
+ const requestResult = request . keys . map < KeyValuePair > ( ( key ) => [
238
+ key ,
239
+ map [ key ] ,
240
+ ] ) ;
241
+ request . callback ?.( null , requestResult ) ;
242
+ request . resolve ?.( requestResult ) ;
243
+ }
244
+ }
245
+ ) ;
246
+ } ,
247
+
248
+ /**
249
+ * This allows you to batch the fetching of items given an array of `key`
250
+ * inputs. Your callback will be invoked with an array of corresponding
251
+ * key-value pairs found.
252
+ *
253
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
254
+ */
255
+ multiGet : ( keys , callback ) => {
256
+ if ( ! _immediate ) {
257
+ _immediate = setImmediate ( ( ) => {
258
+ _immediate = null ;
259
+ AsyncStorage . flushGetRequests ( ) ;
260
+ } ) ;
261
+ }
262
+
263
+ const getRequest : MultiRequest = {
264
+ keys : keys ,
265
+ callback : callback ,
266
+ // do we need this?
267
+ keyIndex : _getKeys . length ,
268
+ } ;
269
+
270
+ const promiseResult = new Promise < readonly KeyValuePair [ ] > (
271
+ ( resolve , reject ) => {
272
+ getRequest . resolve = resolve ;
273
+ getRequest . reject = reject ;
274
+ }
275
+ ) ;
276
+
277
+ _getRequests . push ( getRequest ) ;
278
+ // avoid fetching duplicates
279
+ keys . forEach ( ( key ) => {
280
+ if ( _getKeys . indexOf ( key ) === - 1 ) {
281
+ _getKeys . push ( key ) ;
282
+ }
283
+ } ) ;
284
+
285
+ return promiseResult ;
286
+ } ,
287
+
288
+ /**
289
+ * Use this as a batch operation for storing multiple key-value pairs. When
290
+ * the operation completes you'll get a single callback with any errors.
291
+ *
292
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
293
+ */
294
+ multiSet : ( keyValuePairs , callback ) => {
295
+ checkValidArgs ( keyValuePairs , callback ) ;
296
+ return new Promise ( ( resolve , reject ) => {
297
+ keyValuePairs . forEach ( ( [ key , value ] ) => {
298
+ checkValidInput ( key , value ) ;
299
+ } ) ;
300
+
301
+ RCTAsyncStorage . multiSet ( keyValuePairs , ( errors ?: ErrorLike [ ] ) => {
302
+ const error = convertErrors ( errors ) ;
303
+ callback ?.( error ) ;
304
+ if ( error ) {
305
+ reject ( error ) ;
306
+ } else {
307
+ resolve ( ) ;
308
+ }
309
+ } ) ;
310
+ } ) ;
311
+ } ,
312
+
313
+ /**
314
+ * Call this to batch the deletion of all keys in the `keys` array.
315
+ *
316
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
317
+ */
318
+ multiRemove : ( keys , callback ) => {
319
+ return new Promise ( ( resolve , reject ) => {
320
+ keys . forEach ( ( key ) => checkValidInput ( key ) ) ;
321
+
322
+ RCTAsyncStorage . multiRemove ( keys , ( errors ?: ErrorLike [ ] ) => {
323
+ const error = convertErrors ( errors ) ;
324
+ callback ?.( error ) ;
325
+ if ( error ) {
326
+ reject ( error ) ;
327
+ } else {
328
+ resolve ( ) ;
329
+ }
330
+ } ) ;
331
+ } ) ;
332
+ } ,
333
+
334
+ /**
335
+ * Batch operation to merge in existing and new values for a given set of
336
+ * keys. This assumes that the values are stringified JSON.
337
+ *
338
+ * See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
339
+ */
340
+ multiMerge : ( keyValuePairs , callback ) => {
341
+ return new Promise ( ( resolve , reject ) => {
342
+ RCTAsyncStorage . multiMerge ( keyValuePairs , ( errors ?: ErrorLike [ ] ) => {
343
+ const error = convertErrors ( errors ) ;
344
+ callback ?.( error ) ;
345
+ if ( error ) {
346
+ reject ( error ) ;
347
+ } else {
348
+ resolve ( ) ;
349
+ }
350
+ } ) ;
351
+ } ) ;
352
+ } ,
353
+ } ;
354
+ } ) ( ) ;
355
+
356
+ export default AsyncStorage ;
0 commit comments