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 <mcvey@thecollective-la.com>

Test Plan: Imported from GitHub, without a `Test Plan:` line.
This commit is contained in:
Rob McVey 2015-04-07 02:02:05 -07:00
parent dc5be73a94
commit 2aa52880b7
2 changed files with 139 additions and 76 deletions

View File

@ -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) {

View File

@ -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<string>,
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => 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<Array<string>>,
callback: ?(errors: ?Array<Error>) => 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<string>,
callback: ?(errors: ?Array<Error>) => 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<Array<string>>,
callback: ?(errors: ?Array<Error>) => 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);
}
});
});
},
};