Skip to content

Instantly share code, notes, and snippets.

@cgfeel
Last active February 12, 2019 06:51
Show Gist options
  • Save cgfeel/22387f5cc5788931c23822d78a1a8e71 to your computer and use it in GitHub Desktop.
Save cgfeel/22387f5cc5788931c23822d78a1a8e71 to your computer and use it in GitHub Desktop.
React Native 本地存储优化,基于`react-native-storage`,提供:① 缓存数据,定期删除;② 本地没有数据的时候请求远程数据;③ 后台静默更新数据
/**
* 数据存储工具
*/
import Storage from 'react-native-storage';
import {AsyncStorage} from 'react-native';
const storage = new Storage({
// 最大容量,默认值1000条数据循环存储
size: 1000,
// 存储引擎:对于RN使用AsyncStorage,对于web使用window.localStorage
// 如果不指定则数据只会保存在内存中,重启后即丢失
storageBackend: AsyncStorage,
// 数据过期时间,默认一整天(1000 * 3600 * 24 毫秒),设为null则永不过期
defaultExpires: 1000 * 3600 * 24,
// 读写时在内存中缓存数据。默认启用
enableCache: true,
// 你可以在构造函数这里就写好sync的方法
// 或是在任何时候,直接对storage.sync进行赋值修改
// 或是写到另一个文件里,这里require引入
// 如果storage中没有相应数据,或数据已过期,
// 则会调用相应的sync方法,无缝返回最新数据
});
// 如果请求的`key`不存在,且`storage.sync`也不存在,那么返回一个错误
/**
* 加载缓存,请求参数
* init:请求参数
* point|id|expires: 作为存储数据的值得,其中`point`作为存储的`key`
* body: 作为向web请求的值,所有非`body`的键名都作为存储数据时候的值
* reback: 钩子参数,如果是`true`,将删除本地数据重新获取,如果是对象参数如下
* remove: 如果是`true`,将删除本地数据重新获取,禁止后台更新
* flag: 数据处理钩子,在线上获取到数据后,本地进行处理
* uptime: 后台定时更新,目的在数据没有过期的情况下定时检查线上最新数据
*/
storage.loadSync = (init, remove) => {
let data = {key: init.point};
if (init.id) {
data.id = init.id;
}
const reback = remove||{};
const flush = reback.remove||reback === true;
flush && storage.remove(data);
let option = {body: init.body||{}};
init.method = 'POST';
for (let i in init) {
if (i === 'body') {
continue;
} else if (['point', 'id', 'expires'].indexOf(i) < 0) {
option[i] = init[i];
} else {
option.body[i] = init[i];
}
}
const method = {
key: init.point,
id: init.id||undefined,
autoSync: true,
syncParams: {
extraFetchOptions: option,
someFlag: reback.flag,
expires: init.expires
}
};
return storage.load(method).then(info => {
// 后台定时更新,要求
// 没有被强制更新;有设置更新时间;有存在更新方法;当前时间已经超过了更新时间
const uptime = info.uptime||0;
if (!flush && reback.uptime && storage.sync[init.point] &&
(new Date().getTime() - uptime) > reback.uptime) {
storage.sync[init.point](method);
}
return info;
}).catch(err => {
err.error = 1;
switch(err.name) {
case 'NotFoundError':
data.log = '请求了一个不存在的`key`';
break;
case 'ExpiredError':
err.log = '数据可能过期了';
break;
default:
err.log = '请求失败了';
}
return err;
});
};
/**
* 获取数据集合,参数按照`storage.loadSync`来,有一点不同
* 如果需要存储远程数据,`reback.flag` 返回的值一定是{id: object}对象
* 其中`id`是存储的ID,`object`是存储的数据
*/
storage.allDataSync = async (init, remove) => {
let data = {key: init.point};
if (init.id) {
data.id = init.id;
}
const reback = remove||{};
const flush = reback.remove||reback === true;
let ids = [];
let allsync = {error: 1, cost: 0, uptime: 0};
if (flush) {
storage.remove(data);
} else {
allsync = await storage.loadSync({
point: init.point + 'AllSync'
});
ids = await storage.getIdsForKey(init.point);
}
let results = [];
const cache = !flush && allsync.error !== 1 && ids.length && allsync.cost === ids.length;
if (cache)
{
results = await storage.getAllDataForKey(init.point);
}
// 如果有缓存的情况下,没有后台更新时间或者是还未到更新时间,直接返还缓存数据
const uptime = reback.uptime && (new Date().getTime() - allsync.uptime) > reback.uptime;
if (cache && !uptime) {
return results;
}
let option = {body: init.body||{}};
init.method = 'POST';
for (let i in init) {
if (i === 'body') {
continue;
} else if (['point', 'id', 'expires'].indexOf(i) < 0) {
option[i] = init[i];
} else {
option.body[i] = init[i];
}
}
// 如果是启用缓存数据,则后台更新返回缓存数据,否则加载远程数据
if (cache) {
fetchAllData({
extraFetchOptions: option,
someFlag: reback.flag,
expires: init.expires
});
} else {
results = await fetchAllData({
extraFetchOptions: option,
someFlag: reback.flag,
expires: init.expires
});
}
return results;
};
storage.userToken = call => {
storage.loadSync({
point: 'userToken'
}).then(call);
};
storage.URL = 'https://example.com/api/';
storage.SECONDS = 1000;
storage.MINUTE_IN_SECONDS = storage.SECONDS * 60;
storage.HOUR_IN_SECONDS = storage.MINUTE_IN_SECONDS * 60;
storage.DAY_IN_SECONDS = storage.HOUR_IN_SECONDS * 24;
storage.WEEK_IN_SECONDS = storage.DAY_IN_SECONDS * 7;
storage.MONTH_IN_SECONDS = storage.DAY_IN_SECONDS * 30;
storage.YEAR_IN_SECONDS = storage.DAY_IN_SECONDS * 365;
const fetchData = async params => {
const {
id,
syncParams: {
extraFetchOptions, someFlag, expires
}
} = params;
const point = extraFetchOptions.body.point;
extraFetchOptions.body = JSON.stringify(extraFetchOptions.body);
const options = Object.assign({
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
}, extraFetchOptions);
const response = await fetch(storage.URL, options);
const responseText = await response.text();
// 处理需要的数据
let json = JSON.parse(responseText);
let method = {
key: point,
expires: expires === null ? null : (expires||86400) * 1000
};
if (json) {
json.uptime = new Date().getTime();
if (someFlag) {
json = someFlag(json);
}
method.data = json;
if (id) {
method.id = id;
}
storage.save(method);
return json;
} else {
// 出错抛出异常
throw new Error(`error syncing buyer`);
}
};
const fetchAllData = async params => {
const {
extraFetchOptions, someFlag, expires
} = params;
const point = extraFetchOptions.body.point;
extraFetchOptions.body = JSON.stringify(extraFetchOptions.body);
const options = Object.assign({
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
}, extraFetchOptions);
const response = await fetch(storage.URL, options);
const responseText = await response.text();
// 处理需要的数据
let json = JSON.parse(responseText);
let method = {
key: point,
expires: expires === null ? null : (expires||86400) * 1000
};
if (json) {
let list = [];
if (someFlag) {
const saveData = someFlag(json);
for (let i in saveData) {
list.push(saveData[i]);
method.data = saveData[i];
method.id = i;
storage.save(method);
}
// 当前值记录一个数据,以便更新使用
const total = Object.keys(saveData).length;
if (total) {
storage.save({
key: point + 'AllSync',
expires: expires === null ? null : (expires||86400) * 1000,
data: {
uptime: new Date().getTime(),
cost: total,
error: 0
}
});
}
} else {
return json;
}
return list;
} else {
// 出错抛出异常
throw new Error(`error syncing buyer`);
}
};
storage.sync = {
buyerList: fetchData,
imageCode: fetchData
}
global.Storage = storage;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment