123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @format
- * @noflow
- * @flow-weak
- * @jsdoc
- */
- 'use strict';
- import NativeAsyncStorage from './NativeAsyncStorage';
- import invariant from 'invariant';
- // Use SQLite if available, otherwise file storage.
- const RCTAsyncStorage = NativeAsyncStorage;
- /**
- * `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
- * storage system that is global to the app. It should be used instead of
- * LocalStorage.
- *
- * See https://reactnative.dev/docs/asyncstorage.html
- */
- const AsyncStorage = {
- _getRequests: ([]: Array<any>),
- _getKeys: ([]: Array<string>),
- _immediate: (null: ?number),
- /**
- * Fetches an item for a `key` and invokes a callback upon completion.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#getitem
- */
- getItem: function(
- key: string,
- callback?: ?(error: ?Error, result: ?string) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiGet([key], function(errors, result) {
- // Unpack result to get value from [[key,value]]
- const value = result && result[0] && result[0][1] ? result[0][1] : null;
- const errs = convertErrors(errors);
- callback && callback(errs && errs[0], value);
- if (errs) {
- reject(errs[0]);
- } else {
- resolve(value);
- }
- });
- });
- },
- /**
- * Sets the value for a `key` and invokes a callback upon completion.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#setitem
- */
- setItem: function(
- key: string,
- value: string,
- callback?: ?(error: ?Error) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiSet([[key, value]], function(errors) {
- const errs = convertErrors(errors);
- callback && callback(errs && errs[0]);
- if (errs) {
- reject(errs[0]);
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Removes an item for a `key` and invokes a callback upon completion.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#removeitem
- */
- removeItem: function(
- key: string,
- callback?: ?(error: ?Error) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiRemove([key], function(errors) {
- const errs = convertErrors(errors);
- callback && callback(errs && errs[0]);
- if (errs) {
- reject(errs[0]);
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Merges an existing `key` value with an input value, assuming both values
- * are stringified JSON.
- *
- * **NOTE:** This is not supported by all native implementations.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#mergeitem
- */
- mergeItem: function(
- key: string,
- value: string,
- callback?: ?(error: ?Error) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiMerge([[key, value]], function(errors) {
- const errs = convertErrors(errors);
- callback && callback(errs && errs[0]);
- if (errs) {
- reject(errs[0]);
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
- * don't want to call this; use `removeItem` or `multiRemove` to clear only
- * your app's keys.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#clear
- */
- clear: function(callback?: ?(error: ?Error) => void): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.clear(function(error) {
- callback && callback(convertError(error));
- if (error && convertError(error)) {
- reject(convertError(error));
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Gets *all* keys known to your app; for all callers, libraries, etc.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#getallkeys
- */
- getAllKeys: function(
- callback?: ?(error: ?Error, keys: ?Array<string>) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.getAllKeys(function(error, keys) {
- callback && callback(convertError(error), keys);
- if (error) {
- reject(convertError(error));
- } else {
- resolve(keys);
- }
- });
- });
- },
- /**
- * The following batched functions are useful for executing a lot of
- * operations at once, allowing for native optimizations and provide the
- * convenience of a single callback after all operations are complete.
- *
- * These functions return arrays of errors, potentially one for every key.
- * For key-specific errors, the Error object will have a key property to
- * indicate which key caused the error.
- */
- /**
- * Flushes any pending requests using a single batch call to get the data.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#flushgetrequests
- * */
- flushGetRequests: function(): void {
- const getRequests = this._getRequests;
- const getKeys = this._getKeys;
- this._getRequests = [];
- this._getKeys = [];
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- RCTAsyncStorage.multiGet(getKeys, function(errors, result) {
- // Even though the runtime complexity of this is theoretically worse vs if we used a map,
- // it's much, much faster in practice for the data sets we deal with (we avoid
- // allocating result pair arrays). This was heavily benchmarked.
- //
- // Is there a way to avoid using the map but fix the bug in this breaking test?
- // https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
- const map = {};
- result &&
- result.forEach(([key, value]) => {
- map[key] = value;
- return value;
- });
- const reqLength = getRequests.length;
- for (let i = 0; i < reqLength; i++) {
- const request = getRequests[i];
- const requestKeys = request.keys;
- const requestResult = requestKeys.map(key => [key, map[key]]);
- request.callback && request.callback(null, requestResult);
- request.resolve && request.resolve(requestResult);
- }
- });
- },
- /**
- * This allows you to batch the fetching of items given an array of `key`
- * inputs. Your callback will be invoked with an array of corresponding
- * key-value pairs found.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#multiget
- */
- multiGet: function(
- keys: Array<string>,
- callback?: ?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void,
- ): Promise {
- if (!this._immediate) {
- this._immediate = setImmediate(() => {
- this._immediate = null;
- this.flushGetRequests();
- });
- }
- const getRequest = {
- keys: keys,
- callback: callback,
- // do we need this?
- keyIndex: this._getKeys.length,
- resolve: null,
- reject: null,
- };
- const promiseResult = new Promise((resolve, reject) => {
- getRequest.resolve = resolve;
- getRequest.reject = reject;
- });
- this._getRequests.push(getRequest);
- // avoid fetching duplicates
- keys.forEach(key => {
- if (this._getKeys.indexOf(key) === -1) {
- this._getKeys.push(key);
- }
- });
- return promiseResult;
- },
- /**
- * Use this as a batch operation for storing multiple key-value pairs. When
- * the operation completes you'll get a single callback with any errors.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#multiset
- */
- multiSet: function(
- keyValuePairs: Array<Array<string>>,
- callback?: ?(errors: ?Array<Error>) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
- const error = convertErrors(errors);
- callback && callback(error);
- if (error) {
- reject(error);
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Call this to batch the deletion of all keys in the `keys` array.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#multiremove
- */
- multiRemove: function(
- keys: Array<string>,
- callback?: ?(errors: ?Array<Error>) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiRemove(keys, function(errors) {
- const error = convertErrors(errors);
- callback && callback(error);
- if (error) {
- reject(error);
- } else {
- resolve(null);
- }
- });
- });
- },
- /**
- * Batch operation to merge in existing and new values for a given set of
- * keys. This assumes that the values are stringified JSON.
- *
- * **NOTE**: This is not supported by all native implementations.
- *
- * See https://reactnative.dev/docs/asyncstorage.html#multimerge
- */
- multiMerge: function(
- keyValuePairs: Array<Array<string>>,
- callback?: ?(errors: ?Array<Error>) => void,
- ): Promise {
- invariant(RCTAsyncStorage, 'RCTAsyncStorage not available');
- return new Promise((resolve, reject) => {
- RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
- const error = convertErrors(errors);
- callback && callback(error);
- if (error) {
- reject(error);
- } else {
- resolve(null);
- }
- });
- });
- },
- };
- // Not all native implementations support merge.
- if (!RCTAsyncStorage.multiMerge) {
- delete AsyncStorage.mergeItem;
- delete AsyncStorage.multiMerge;
- }
- function convertErrors(errs) {
- if (!errs) {
- return null;
- }
- return (Array.isArray(errs) ? errs : [errs]).map(e => convertError(e));
- }
- function convertError(error) {
- if (!error) {
- return null;
- }
- const out = new Error(error.message);
- out.key = error.key; // flow doesn't like this :(
- return out;
- }
- module.exports = AsyncStorage;
|