@po-to/pt-cache
v1.1.0
Published
a shim for operate browser cache
Downloads
5
Readme
pt-cache
a shim library for operate browser storage
- 项目主页:po-to.org/pt-cache
- 项目地址:Github
- API文档: 在线文档 或见 /docs
- 案例应用:在线文档 或见 /examples
- 概述简介
- 使用说明
为方便描述说明,以下可能会使用typescript或es6的糖衣语法,实际使用本库时并不要求使用typescript或es6
简介
关于网页的cache,我们知道HTTP协议中处理缓存的机制一般有:Expires、Last-Modified、Etag等,由浏览器实现并封装,不可以编程方式访问。
H5提供由manifest指定的AppCache,可对站点文件的离线存储进行简单配制,使用场景很有限。
在实际开发中,我们经常需要可编程控制的数据级cache,基于H5为我们提供了sessionStorage和localStorage,在此之上进行抽象与封装,因此产生了此框架。
更广泛的概念
我们假定缓存除了创建时设定的有效条件和周期,还存在不同类别的生命周期:
- memoryStorage -- 存于内存中,即JS变量,刷新页面将消失
- sessionStorage -- 存于浏览器sessionStorage中,关闭浏览器将消失
- localStorage -- 存在于浏览品localStorage中,可认为长久存在
浏览器原生为我们维护了三种不同生命周期的cache,但是却不能在此基础上与缓存自身的周期设定取并集,以此来满足我们在实际开发中某些应用场景。比如:
最新列表,需求希望页面刷新时拉取一次,其后每1小时自动拉取一次,而且点击“立即更新”的按钮,能立即拉取。
如果使用http缓存:
- 最新列表不一定是一个独立的API接口。
- 设定有效期为1小时,但是无法做到页面刷新或点击“立即更新”按钮时立即更新。
我们不得不用setInterval方法:
- 针对这个具体需求,需要具体的编程
- 如果某一天需求变更为半小时拉取一次,我们又不得不翻出这段JS来更改
如果使用pt-cache:
var ptcache = require("po-to/pt-cache"); /* 假设我们有一个取数据的api接口,url是"xxx" */ var url = "xxx"; /* 每1秒拉取1次数据 */ setInterval(function(){ ptcache.load({url:url}).then(function(data){ //对比data并更新dom }); },1000) /* 点击“*立即更新*”按钮,删除该笔缓存即可 */ $("#btn-update").on("click",function(){ ptcache.removeItem(url); }) /* 此时,api可输出一个自定义的http响应头X-Cache=r3600s,r表示该api输出的数据将缓存放入内存,3600s表示缓存1小时 */
这样一来:JS虽然每1秒会去拉取一次数据,但因为我们设定了X-Cache=r3600s的自定义http头,所以在1小时内,JS拉取的只是缓存在内存中的数据。对于前端来说,用一个通用的前后端协议即可简化并节省针对特定业务的编程。而且对于某些高频的适时刷新,访问内存或sessionStorage,远比访问浏览器XMLHttpRequest并命中http协议的cache要高性能得多
除此之外
浏览器原生的memoryStorage、sessionStorage、localStorage不提供缓存命中、失效、回收、溢出、加密机制,本库扩展了以上功能
使用说明
兼容性与扩展性
兼容所有支持sessionStorage、localStorage的浏览器,如IE8
除localStorage外,如果需要可扩展至websql等本地数据库
安装
npm install @po-to/pt-cache --save-dev
define([ "@po-to/pt-cache" ],function( ptcache ){ ... });
目录结构
源文件:./src, 案例:./examples, 文档:./docs
编译
npm start
运行案例
npm run examples
依赖
- 本库并不限制自定义缓存头的具体实现,如借用http协议自定义头X-Cache等,仅提供接口:
interface IRequestResult {
cache?: { type?: CacheType, expired?: string, version?: string, encryption?: boolean },
notModified?: boolean,
dataType: string,
data: any
}
- 本库并不提供外部请求的具体实现,如ajax等,仅提供接口:
interface IRequestOptions {
url: string;
method?: string;
data?:{[key:string]:any};
render?(data:any):any;
headers:{[key:string]:any};
version?: string;
timeout?: number;
}
interface IRequest{
(request: IRequestOptions,success:(data:IRequestResult)=>void,fail:(error:Error)=>void) : void;
}
//用户可自由引入第三方库,如jquery的$.ajax来封装实现以上接口,假设requestFunction就是你实现IRequest接口的外部请求方法,
//调用:ptcache.setConfig({request:requestFunction})来配置使用
- 本库并不提供对缓存写入的字符加密解密的具体实现,仅提供接口:
interface IEncryption {
encrypt: (value: string) => string;
decrypt: (code: string) => string;
}
//用户可自由引入第三方库,如:goolge的CryptoJS.AES,假设myEncryption就是你实现IEncryption接口的方法
/*
ptcache.setConfig(encryption:{
encode : function(str){
var key = 'dsfsdfsdfe';//key可由服务端生成
var iv = key.substr(0,16);
key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
str = CryptoJS.AES.encrypt(str,key,{iv:iv,padding:CryptoJS.pad.ZeroPadding});
return str.ciphertext.toString(CryptoJS.enc.Base64);
},
decode : function(str){
var key = 'dsfsdfsdfe';//key可由服务端生成
var iv = key.substr(0,16);
key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
str = CryptoJS.AES.decrypt(str,key,{iv:iv,padding:CryptoJS.pad.ZeroPadding});
return CryptoJS.enc.Utf8.stringify(str);
}
})
*/
- 本库仅提供一种序列化对象进行存储的序列化器:json,如果你需要别的序列化器,请实现接口:
interface ISerialization {
decode: (str: string) => any;
encode: (data: any) => string;
}
/*
比如,你想在缓存中存入xml对象,请调用:ptcache.setConfig({
serializations:{
xml : {
decode: (str: string) => xml //具体请自行实现
encode: (data: xml) => string //具体请自行实现
}
}
})
*/
- 由于本库提供load()方法集成代理外部请求,调用此方法前,可设置异步计数器ITaskCounter来实现异步请求计数,从而实现如全局loading等效果。发起一个异步请求:计数器+1;完成一个异步请求:计数器-1;
本库不直接提供计数器,仅提供接口ITaskCounter,调用load方法发起异步请求时,如果有设置TaskCounter,则会触发ITaskCounter.addItem方法,将异步清求的promise传入计数器
/**
* 如果要实现异步请求计数,请设置实现该接口的TaskCounter´.
* @param promise 异步请求返回的Promise对象
* @param note 异步请求的注解
*/
addItem(promise:Promise<any>,note?:string):void;
/*
比如,你如果使用@po-to/tomato库,可直接调用:ptcache.setConfig({
taskCounter: tomato.taskCounter
})
*/
API说明
写入一笔cache
如:ptcache.setItem("list",new ptcache.CacheContent([...]))
/*
@param key:string 为cache的key
@param content: CacheContent 为cache的值,稍后会详细说明
@param type?: CacheType 为cache类型,取值为0,1,2 ,默认为0,即内存型
enum CacheType {
Ram = 0, 内存型
Session = 1, //会话型
Local = 2, //持久型
}
@return boolean 是否创建成功
*/
function setItem(key: string, content: CacheContent, type?: CacheType): boolean;
ptcache.CacheContent是本库的一个类,写入缓存的数据必须用这个类包装:
class CacheContent {
/*
@param data?: any 为要写入cache的值,如果data为null或是undefined,表示不写入data,即表示更新某cache
@param dataType?: string 为data值的类型,由于sessionStorage和localStorage只能存储文本,当data值不为文本时,本库将根据此dataType来序列化和反序列化,此默认值为"json"
@param expired?: string 为cache的过期时间
如果expired为一个数字描述,代表相对时间:
expired > 0 表示expired秒之后过期
expired = 0 表示立即过期,等于写入缓存无效
expired < 0 表示永不过期,除非缓存类型大生命周期结束
如果expired为一个标准的日期描述,即可以用JS内置的new Date(expired)转化为时间,则表示到了expired时间后过期
@param version?: string 为需要的版本验证,类似于http协议中的ETag。
如果一笔cache在有效期内,将直接命中并返回该笔cache
如果一笔cache已经失效:
如果这笔cache无version描述,则表示命中失败,取出来的cache为null
如果这笔cache有version描述,则表示该笔cache需要重新通过版本进行验证,验证结果有可能是not modified或是失效
@param encryption?: boolean 是否需要加密存储,如果为true,本库将调用Encryption.encrypt()或decrypt()方法加、解密之后再储存。
本库不直接提供加解密的实现,但提供接口:
interface IEncryption {
encrypt: (value: string) => string;
decrypt: (code: string) => string;
}
用户可自行引用第三方加解密库,如:google的CryptoJS,使用案例中有演示。
使用此参数前,用户需要先调用setConfig({encryption:myEncryption})设置加解密方法
*/
constructor(data?: any, dataType?: string, expired?: string, version?: string, encryption?: boolean);
}
读取一笔cache
如:var result = ptcache.getItem("list");
/*
@param key: string 要获取的缓存key
@param type?: CacheType 要从何种缓存池中获取,通常可不传,本库将按memoryStorage > sessionStorage > localStorage依次查找
@return CacheResult | null 返回为null表示未命中缓存,返回为CacheResult解释如下:
class CacheResult {
readonly value: string; //cache序列化后的原始值,为字符串
readonly version: string; //该cache的版本描述
readonly from: CacheType; //该cache来自于哪个类型,参见CacheType
toData(): any; //取出最终的cache值
}
从此可看出,getItem取出的cache并不是最终的缓存值,而是经过包装的CacheResult实例对象,调用此对象的toData()方法,才可以得到最终值。
如果CacheResult实例对象的version属性为空,表示该cache是有效的;反之,表示虽然命中的cache,但还不一定是最终有效,需要根据此version值确认
*/
function getItem(key: string, type?: CacheType): CacheResult | null;
更新一笔cache
如:ptcache.setItem("list",new ptcache.CacheContent(null,null,100))
本库不直接提供更新一笔cache的方法,用户可直接调用setItem重设即可; 如果需要更新的仅仅是过期时间或是版本标识,不需要更新内容,在调用setItem方法的时候,new ptcache.CacheContent(data,dataType,expired,version,encryption),其中data,dataType传入null值即可; 如 ptcache.setItem("list",new ptcache.CacheContent(null,null,100)),就表示将key为"list"的这笔cache往后增加100秒有效期
删除一笔cache
如:ptcache.removeItem("list");
/*
@param key: string 要删除的缓存key
@param type?: CacheType 要从何种缓存池中删除,通常可不传,本库将按memoryStorage > sessionStorage > localStorage依次查找
*/
function removeItem(key: string, type?: CacheType): void;
清空所有cache
如:ptcache.clear(1);
/*
@param type?: CacheType 要清空何种缓存池,不传为清空所有三种缓存
*/
function clear(type?: CacheType): void;
与外部请求相结合
如:ptcache.load({url:"xxx"}).then(function(data){...});
/*
@param requestOptions: IRequestOptions 发起外部请求的数据,该数据将传入第三方外部请求实现API:IRequest,进行外部请求
interface IRequestOptions {
url: string; //请求地址
method?: string; //方法,如"get","post","put","delete"
data?: { //请求参数
[key: string]: any;
};
render?(data: any): any; //对请求结果加工
headers: { //请求头设置
[key: string]: any;
};
version?: string; //版本验证
timeout?: number; //外部请求的超时时间
note?:string; //加载信息说明
hideLoading?:boolean; //如果设置了taskCounter,该参数控制是否将该请求加入异步计数器
}
@param succss?: (data: any) => void 请求成功回调
@param fail?: (error: Error) => void) 请求失败回调
@return Promise<any>
*/
function load(requestOptions: IRequestOptions, succss?: (data: any) => void, fail?: (error: Error) => void): Promise<any>;
注意:ptcache.load方法封装了ptcache.getItem方法,在发起外部请求之前,会查询缓存中是否存在以url为key的缓存,如果存在,则进一步验证version的有效性。如果验证有效,则不发起真实的外部请求,而返回cache值 所以回调函数中接受的data: any,是最终的值,而非CacheResult实例对象。
外部请求,可由server端输出一个自定义的responseHeader X-Cache,值为"1,3600,Wed Dec 21 2016 17:25:57",表示要将该数据放入sessionStorage中缓存,缓存有效期是3600秒,版本识别号为Wed Dec 21 2016 17:25:57,缓存到期后将version:Wed Dec 21 2016 17:25:57发送回server进行验证,如果无需更新,server可返回304,(Not Modified),并重新给该缓存增加有效期
config配置
如:ptcache.setConfig({namespace:"$#@"}})
function setConfig(options: {
namespace?: string; //在存入sessionStorage或是localStorage时,每一笔cache都会以此namespace为前缀,避免冲突
ramStorageLimit?: number //内存型缓存最大占用内存空间,默认为0,表示无限制
encryption?: IEncryption; // 加解密方法,由第三方库提供
mappingKey?: (key: string) => string; //每笔cache的key,可以经过此方法映射,比如以url为key进行写入会太长,可将url进行md5之后再作为key写入
/*
由于sessionStorage或是localStorage只能储存文本,所以存入数据非文本时,将跟据dataType来调用此序列化方法进行序列化与反序列化
interface ISerialization {
decode: (str: string) => any;
encode: (data: any) => string;
}
本库默认提供一种ISerialization,名字为"json"
*/
serializations?: {
string: ISerialization;
};
//外部请求的方法,由第三方库提供
request?: IRequest
}): void;
缓存空间的回收与溢出处理
内部自动执行
设置一笔缓存时,sessionStorage和localStorage在写满溢出时会抛出一个错误,本库会捕获这个错误,之后会采取回收策略
memoryStorage不会产生溢出错误,可自行设置一个存储上限值(本库会将所有写入memoryStorage的每笔cache的key,value长度求和,作为占用空间值),超过这个值后,本库会采取回收策略。
用户可以通过setConfig({ramStorageLimit: 1024*20})来设置此值,由于js无法真正管理内存,所以此值单位为字符个数,并不精准
回收策略为:
- 先将缓存池中所有失效的缓存清除,如果依然不够存储空间
- 将缓存池中所有缓存按访问时间排序,回收较久没访问的冷门数据