From 2aa52880b73af86daa069232fb38ad2249568ec1 Mon Sep 17 00:00:00 2001 From: Rob McVey Date: Tue, 7 Apr 2015 02:02:05 -0700 Subject: [PATCH] Add promise support to AsyncStorage Summary: Since `AsyncStorage` is the primary cache, it would be nice to stick with fetch's promise model and make the common use-case of: 1) check cache 2) make request if cache is invalid more straightforward. Currently if I want to check a cache prior to using fetch (or another promise-based XHR lib) I have to provide a callback. I left the callback support and `resolve`/`reject` the promise after the callback has been applied. Closes https://github.com/facebook/react-native/pull/593 Github Author: Rob McVey Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/AsyncStorageExample.js | 43 +++--- Libraries/Storage/AsyncStorage.ios.js | 172 ++++++++++++++------- 2 files changed, 139 insertions(+), 76 deletions(-) diff --git a/Examples/UIExplorer/AsyncStorageExample.js b/Examples/UIExplorer/AsyncStorageExample.js index 323cedd15..8bc1631cd 100644 --- a/Examples/UIExplorer/AsyncStorageExample.js +++ b/Examples/UIExplorer/AsyncStorageExample.js @@ -29,16 +29,17 @@ var COLORS = ['red', 'orange', 'yellow', 'green', 'blue']; var BasicStorageExample = React.createClass({ componentDidMount() { - AsyncStorage.getItem(STORAGE_KEY, (error, value) => { - if (error) { - this._appendMessage('AsyncStorage error: ' + error.message); - } else if (value !== null) { - this.setState({selectedValue: value}); - this._appendMessage('Recovered selection from disk: ' + value); - } else { - this._appendMessage('Initialized with no selection on disk.'); - } - }); + AsyncStorage.getItem(STORAGE_KEY) + .then((value) => { + if (value !== null){ + this.setState({selectedValue: value}); + this._appendMessage('Recovered selection from disk: ' + value); + } else { + this._appendMessage('Initialized with no selection on disk.'); + } + }) + .catch((error) => this._appendMessage('AsyncStorage error: ' + error.message)) + .done(); }, getInitialState() { return { @@ -81,23 +82,17 @@ var BasicStorageExample = React.createClass({ _onValueChange(selectedValue) { this.setState({selectedValue}); - AsyncStorage.setItem(STORAGE_KEY, selectedValue, (error) => { - if (error) { - this._appendMessage('AsyncStorage error: ' + error.message); - } else { - this._appendMessage('Saved selection to disk: ' + selectedValue); - } - }); + AsyncStorage.setItem(STORAGE_KEY, selectedValue) + .then(() => this._appendMessage('Saved selection to disk: ' + selectedValue)) + .catch((error) => this._appendMessage('AsyncStorage error: ' + error.message)) + .done(); }, _removeStorage() { - AsyncStorage.removeItem(STORAGE_KEY, (error) => { - if (error) { - this._appendMessage('AsyncStorage error: ' + error.message); - } else { - this._appendMessage('Selection removed from disk.'); - } - }); + AsyncStorage.removeItem(STORAGE_KEY) + .then(() => this._appendMessage('Selection removed from disk.')) + .catch((error) => { this._appendMessage('AsyncStorage error: ' + error.message) }) + .done(); }, _appendMessage(message) { diff --git a/Libraries/Storage/AsyncStorage.ios.js b/Libraries/Storage/AsyncStorage.ios.js index aa48e40de..5ee2dc5e3 100644 --- a/Libraries/Storage/AsyncStorage.ios.js +++ b/Libraries/Storage/AsyncStorage.ios.js @@ -27,49 +27,73 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncLocalStorage; * operates globally. * * This JS code is a simple facad over the native iOS implementation to provide - * a clear JS API, real Error objects, and simple non-multi functions. + * a clear JS API, real Error objects, and simple non-multi functions. Each + * method returns a `Promise` object. */ var AsyncStorage = { /** * Fetches `key` and passes the result to `callback`, along with an `Error` if - * there is any. + * there is any. Returns a `Promise` object. */ getItem: function( key: string, callback: (error: ?Error, result: ?string) => void - ): void { - RCTAsyncStorage.multiGet([key], function(errors, result) { - // Unpack result to get value from [[key,value]] - var value = (result && result[0] && result[0][1]) ? result[0][1] : null; - callback((errors && convertError(errors[0])) || null, value); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiGet([key], function(errors, result) { + // Unpack result to get value from [[key,value]] + var value = (result && result[0] && result[0][1]) ? result[0][1] : null; + callback && callback((errors && convertError(errors[0])) || null, value); + if (errors) { + reject(convertError(errors[0])); + } else { + resolve(value); + } + }); }); }, /** * Sets `value` for `key` and calls `callback` on completion, along with an - * `Error` if there is any. + * `Error` if there is any. Returns a `Promise` object. */ setItem: function( key: string, value: string, callback: ?(error: ?Error) => void - ): void { - RCTAsyncStorage.multiSet([[key,value]], function(errors) { - callback && callback((errors && convertError(errors[0])) || null); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiSet([[key,value]], function(errors) { + callback && callback((errors && convertError(errors[0])) || null); + if (errors) { + reject(convertError(errors[0])); + } else { + resolve(null); + } + }); }); }, - + /** + * Returns a `Promise` object. + */ removeItem: function( key: string, callback: ?(error: ?Error) => void - ): void { - RCTAsyncStorage.multiRemove([key], function(errors) { - callback && callback((errors && convertError(errors[0])) || null); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiRemove([key], function(errors) { + callback && callback((errors && convertError(errors[0])) || null); + if (errors) { + reject(convertError(errors[0])); + } else { + resolve(null); + } + }); }); }, /** - * Merges existing value with input value, assuming they are stringified json. + * Merges existing value with input value, assuming they are stringified json. Returns a `Promise` object. * * Not supported by all native implementations. */ @@ -77,29 +101,50 @@ var AsyncStorage = { key: string, value: string, callback: ?(error: ?Error) => void - ): void { - RCTAsyncStorage.multiMerge([[key,value]], function(errors) { - callback && callback((errors && convertError(errors[0])) || null); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiMerge([[key,value]], function(errors) { + callback && callback((errors && convertError(errors[0])) || null); + if (errors) { + reject(convertError(errors[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 - * own keys instead. + * own keys instead. Returns a `Promise` object. */ - clear: function(callback: ?(error: ?Error) => void) { - RCTAsyncStorage.clear(function(error) { - callback && callback(convertError(error)); + clear: function(callback: ?(error: ?Error) => void): Promise { + 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 the system, for all callers, libraries, etc. + * Gets *all* keys known to the system, for all callers, libraries, etc. Returns a `Promise` object. */ - getAllKeys: function(callback: (error: ?Error) => void) { - RCTAsyncStorage.getAllKeys(function(error, keys) { - callback(convertError(error), keys); + getAllKeys: function(callback: (error: ?Error) => void): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.getAllKeys(function(error, keys) { + callback && callback(convertError(error), keys); + if (error) { + reject(convertError(error)); + } else { + resolve(keys); + } + }); }); }, @@ -115,67 +160,90 @@ var AsyncStorage = { /** * multiGet invokes callback with an array of key-value pair arrays that - * matches the input format of multiSet. + * matches the input format of multiSet. Returns a `Promise` object. * * multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']]) */ multiGet: function( keys: Array, callback: (errors: ?Array, result: ?Array>) => void - ): void { - RCTAsyncStorage.multiGet(keys, function(errors, result) { - callback( - (errors && errors.map((error) => convertError(error))) || null, - result - ); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiGet(keys, function(errors, result) { + var error = (errors && errors.map((error) => convertError(error))) || null; + callback && callback(error, result); + if (errors) { + reject(error); + } else { + resolve(result); + } + }); }); }, /** * multiSet and multiMerge take arrays of key-value array pairs that match - * the output of multiGet, e.g. + * the output of multiGet, e.g. Returns a `Promise` object. * * multiSet([['k1', 'val1'], ['k2', 'val2']], cb); */ multiSet: function( keyValuePairs: Array>, callback: ?(errors: ?Array) => void - ): void { - RCTAsyncStorage.multiSet(keyValuePairs, function(errors) { - callback && callback( - (errors && errors.map((error) => convertError(error))) || null - ); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiSet(keyValuePairs, function(errors) { + var error = (errors && errors.map((error) => convertError(error))) || null; + callback && callback(error); + if (errors) { + reject(error); + } else { + resolve(null); + } + }); }); }, /** - * Delete all the keys in the `keys` array. + * Delete all the keys in the `keys` array. Returns a `Promise` object. */ multiRemove: function( keys: Array, callback: ?(errors: ?Array) => void - ): void { - RCTAsyncStorage.multiRemove(keys, function(errors) { - callback && callback( - (errors && errors.map((error) => convertError(error))) || null - ); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiRemove(keys, function(errors) { + var error = (errors && errors.map((error) => convertError(error))) || null; + callback && callback(error); + if (errors) { + reject(error); + } else { + resolve(null); + } + }); }); }, /** * Merges existing values with input values, assuming they are stringified - * json. + * json. Returns a `Promise` object. * * Not supported by all native implementations. */ multiMerge: function( keyValuePairs: Array>, callback: ?(errors: ?Array) => void - ): void { - RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) { - callback && callback( - (errors && errors.map((error) => convertError(error))) || null - ); + ): Promise { + return new Promise((resolve, reject) => { + RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) { + var error = (errors && errors.map((error) => convertError(error))) || null; + callback && callback(error); + if (errors) { + reject(error); + } else { + resolve(null); + } + }); }); }, };