Skip to content

Instantly share code, notes, and snippets.

@330k
Last active February 13, 2022 08:07
Show Gist options
  • Save 330k/7aa017d99dbbc6d8fb2502d1a2d29e5a to your computer and use it in GitHub Desktop.
Save 330k/7aa017d99dbbc6d8fb2502d1a2d29e5a to your computer and use it in GitHub Desktop.
/**
* L1, L2の2層のキャッシュ
* @param {string} cache_name Cache APIで使用する名前(cacheName)
* @param {function} parser responseを受け取ってキャッシュに保存する内容を返すコールバック関数(async function可)
* @param {number} l1_cache_size L1キャッシュに保持するURL件数
* @param {number} l2_cache_expiration L2キャッシュの有効期限
*/
function LayeredCache(cache_name, parser, l1_cache_size = 1000, cache_expiration = 30 * 86400 * 1000){
const HEADER_EXPIRATION = "_expire_on";
const l1_cache = [];
let l2_cache = null;
let prepared = false;
caches.open(cache_name).then((cs) => {
l2_cache = cs;
prepared = true;
});
/**
* 指定したURLのデータをparser関数で処理した結果を返す。
* L1にあればL1キャッシュからparserで処理済みの結果を返し、
* 有効期限内のL2キャッシュ(Cache API)があれば、再度parser関数で処理して返す。
* @param {string} url
* @return {Promise}
*/
this.fetch = async function(url){
let data = null;
let fetch_flag = false;
let l1_update_flag = false;
const now = Date.now();
let expiration = now + cache_expiration;
if(!prepared){
// Cache APIの準備ができていなければ例外を投げる
throw new Exception("Cache API not ready");
}
let idx = l1_cache.findIndex((e) => e.url === url);
if(idx){
// L1キャッシュにヒット
// L1キャッシュの先頭に移動(LRU)
l1_cache.unshift(data);
// L1キャッシュの先頭に移動(LRU)
l1_cache.unshift(l1_cache.splice(idx, 1)[0]);
if(now > l1_cache[0].expire_on){
// L1キャッシュで期限切れ
l1_cache.shift();
fetch_flag = true;
}else{
data = l1_cache[0].data;
}
}else{
let response = await l2_cache.match(url);
if((response === undefined)
|| response.headers.get(HEADER_EXPIRATION) === null
|| (now > Number.parseInt(response.headers.get(HEADER_EXPIRATION)))){
// L2キャッシュにない場合、またはL2キャッシュが期限切れの場合
fetch_flag = true;
}else{
data = await parser(response);
expiration = Number.parseInt(response.headers.get(HEADER_EXPIRATION));
l1_update_flag = true;
}
}
if(fetch_flag){
// 通信して取得する
response = await fetch(url);
const copy = response.clone();
const headers = new Headers(copy.headers);
headers.append(HEADER_EXPIRATION, expiration);
const body = await copy.blob();
await l2_cache.put(url, new Response(body, {
status: copy.status,
statusText: copy.statusText,
headers: headers
}));
data = await parser(response);
l1_update_flag = true;
}
if(l1_update_flag){
// L1キャッシュの先頭に保存
l1_cache.unshift({
url: url,
data: data,
expire_on: expiration
});
if(l1_cache.length > l1_cache_size){
l1_cache.length = l1_cache_size;
}
}
return data;
};
return this;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment