[RN] Protect AbstractApp and localStorage initialization
This commit is contained in:
parent
d7dddb2509
commit
62c9762793
|
@ -97,17 +97,22 @@ export class AbstractApp extends Component {
|
|||
};
|
||||
|
||||
/**
|
||||
* This way we make the mobile version wait until the
|
||||
* {@code AsyncStorage} implementation of {@code Storage}
|
||||
* properly initializes. On web it does actually nothing, see
|
||||
* {@link #_initStorage}.
|
||||
* Make the mobile {@code AbstractApp} wait until the
|
||||
* {@code AsyncStorage} implementation of {@code Storage} initializes
|
||||
* fully.
|
||||
*
|
||||
* @private
|
||||
* @see {@link #_initStorage}
|
||||
* @type {Promise}
|
||||
*/
|
||||
this.init = this._initStorage().then(() => {
|
||||
this.setState({
|
||||
route: undefined,
|
||||
store: this._maybeCreateStore(props)
|
||||
});
|
||||
});
|
||||
this._init
|
||||
= this._initStorage()
|
||||
.catch(() => { /* AbstractApp should always initialize! */ })
|
||||
.then(() =>
|
||||
this.setState({
|
||||
route: undefined,
|
||||
store: this._maybeCreateStore(props)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +122,7 @@ export class AbstractApp extends Component {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentWillMount() {
|
||||
this.init.then(() => {
|
||||
this._init.then(() => {
|
||||
const { dispatch } = this._getStore();
|
||||
|
||||
dispatch(appWillMount(this));
|
||||
|
@ -175,7 +180,7 @@ export class AbstractApp extends Component {
|
|||
componentWillReceiveProps(nextProps) {
|
||||
const { props } = this;
|
||||
|
||||
this.init.then(() => {
|
||||
this._init.then(() => {
|
||||
// The consumer of this AbstractApp did not provide a redux store.
|
||||
if (typeof nextProps.store === 'undefined'
|
||||
|
||||
|
@ -236,18 +241,21 @@ export class AbstractApp extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delays app start until the {@code Storage} implementation initialises.
|
||||
* This is instantaneous on web, but is async on mobile.
|
||||
* Delays this {@code AbstractApp}'s startup until the {@code Storage}
|
||||
* implementation of {@code localStorage} initializes. While the
|
||||
* initialization is instantaneous on Web (with Web Storage API), it is
|
||||
* asynchronous on mobile/react-native.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initStorage() {
|
||||
if (typeof window.localStorage._initialized !== 'undefined') {
|
||||
return window.localStorage._initialized;
|
||||
}
|
||||
const localStorageInitializing = window.localStorage._initializing;
|
||||
|
||||
return Promise.resolve();
|
||||
return (
|
||||
typeof localStorageInitializing === 'undefined'
|
||||
? Promise.resolve()
|
||||
: localStorageInitializing);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,14 +30,77 @@ export default class Storage {
|
|||
*/
|
||||
this._keyPrefix = keyPrefix;
|
||||
|
||||
// Perform optional asynchronous initialization.
|
||||
const initializing = this._initializeAsync();
|
||||
|
||||
if (initializing) {
|
||||
// Indicate that asynchronous initialization is under way.
|
||||
this._initializing = initializing;
|
||||
|
||||
// When the asynchronous initialization completes, indicate its
|
||||
// completion.
|
||||
initializing.finally(() => {
|
||||
if (this._initializing === initializing) {
|
||||
this._initializing = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys from this storage.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
for (const key of Object.keys(this)) {
|
||||
this.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this storage.
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {string|null} The value associated with {@code key} or
|
||||
* {@code null}.
|
||||
*/
|
||||
getItem(key) {
|
||||
return this.hasOwnProperty(key) ? this[key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this {@code Storage}
|
||||
* in an async manner. The method is required for the cases where we need
|
||||
* the stored data but we're not sure yet whether this {@code Storage} is
|
||||
* already initialized (e.g. on app start).
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getItemAsync(key) {
|
||||
return (
|
||||
(this._initializing || Promise.resolve())
|
||||
.catch(() => { /* _getItemAsync should always resolve! */ })
|
||||
.then(() => this.getItem(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs asynchronous initialization of this {@code Storage} instance
|
||||
* such as loading all keys from {@link AsyncStorage}.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initializeAsync() {
|
||||
if (typeof this._keyPrefix !== 'undefined') {
|
||||
// Load all previously persisted data items from React Native's
|
||||
// AsyncStorage.
|
||||
|
||||
this._initialized = new Promise(resolve => {
|
||||
return new Promise(resolve => {
|
||||
AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => {
|
||||
// XXX The keys argument of getAllKeys' callback may
|
||||
// or may not be preceded by an error argument.
|
||||
// XXX The keys argument of getAllKeys' callback may or may
|
||||
// not be preceded by an error argument.
|
||||
const keys
|
||||
= getAllKeysCallbackArgs[
|
||||
getAllKeysCallbackArgs.length - 1
|
||||
|
@ -73,46 +136,8 @@ export default class Storage {
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys from this storage.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
for (const key of Object.keys(this)) {
|
||||
this.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this storage.
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {string|null} The value associated with {@code key} or
|
||||
* {@code null}.
|
||||
*/
|
||||
getItem(key) {
|
||||
return this.hasOwnProperty(key) ? this[key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this storage in an
|
||||
* async manner. This method is required for those cases where we need the
|
||||
* stored data but we're not sure yet whether the {@code Storage} is already
|
||||
* initialised or not - e.g. on app start.
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getItemAsync(key) {
|
||||
return new Promise(
|
||||
resolve =>
|
||||
AsyncStorage.getItem(
|
||||
`${String(this._keyPrefix)}${key}`,
|
||||
(error, result) => resolve(result ? result : null)));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue