しばしば JavaScript で API と通信する際、通信を一度だけして、その画面が生きている限りその通信結果をキャッシュ的に使い続ける、という挙動をさせたい時があります。例えば /api/account_options からアカウントオプション情報を一度取ってきたら2回目以降は実際に通信せず、最初に取ってきた結果を返すとかそんな感じです。画面が生きているあいだだけ維持されるキャッシュ的なものです。これは次のようにできます。
// 非同期処理全般に対応した汎用的なキャッシュ付きデータ取得関数 export const executeAndCacheAsync = async ({ asyncFunction, cacheKey }) => { // グローバルキャッシュを初期化 window._cache = window._cache || {}; // キャッシュにデータが存在する場合はそれを返す if (window._cache[cacheKey]) { return window._cache[cacheKey]; } // 進行中の Promise がある場合はそれを返す if (window._cache[`${cacheKey}Promise`]) { return window._cache[`${cacheKey}Promise`]; } // 非同期処理を開始し、Promise をキャッシュ window._cache[`${cacheKey}Promise`] = asyncFunction() .then((data) => { // データをキャッシュに保存 window._cache[cacheKey] = data; window._cache[`${cacheKey}Promise`] = null; // 通信中のPromiseをリセット return data; }) .catch((err) => { // エラーが発生した場合、通信中のPromiseをリセット window._cache[`${cacheKey}Promise`] = null; throw err; }); return window._cache[`${cacheKey}Promise`]; }; // 使用例: fetch を使用したデータ取得 export const fetchWithCache = async (url) => { return executeAndCacheAsync({ asyncFunction: () => fetch(url).then((res) => { if (!res.ok) { throw new Error(`Failed to fetch: ${res.statusText}`); } return res.json(); }), cacheKey: url, // URLをキャッシュキーとして使用 }); }; // 使用例の実行 fetchWithCache("/tmp.json").then((data) => console.log(data)).catch((err) => console.error(err)); fetchWithCache("/tmp.json").then((data) => console.log(data)).catch((err) => console.error(err)); fetchWithCache("/tmp.json").then((data) => console.log(data)).catch((err) => console.error(err)); fetchWithCache("/tmp.json").then((data) => console.log(data)).catch((err) => console.error(err)); // 結果は4個でるけど通信は1個だけ
例では fetch にしてますが非同期関数ならば何でも使えます。仕組みとしては以下になります。
- 処理が呼ばれた時点で非同期関数実行の Promise をグローバルな領域に保持する
- 非同期処理が完了したら結果をグローバルな領域に保持する
- 非同期処理が呼ばれた時に結果があればそれを、結果がなく実行中処理のPromiseがあればそれを返し、何もなければ実行数るする
こうすると非同期処理が極短時間の間に複数回走っても1度だけの実行で済み、リソースを消耗しません。例のコードでは実行中のPromiseや実行結果の置き場に window を使いましたが、一つのキャッシュ的な領域を複数の実行の中で共有できれば window である必要はありません。