本文由特邀作者Peter Bengtsson撰寫。 SitePoint特邀文章旨在為您帶來來自JavaScript社區知名作家和演講者的精彩內容
本文演示瞭如何實現已提取請求的本地緩存,以便如果重複執行,則從會話存儲中讀取。這樣做的好處是,您無需為要緩存的每個資源編寫自定義代碼。
如果您想在下次JavaScript聚會上炫耀一番,展示您在處理Promise、最先進的API和本地存儲方面的各種技能,請繼續閱讀。
cachedFetch
封裝了標準的fetch
調用,可以根據內容類型和URL自動緩存響應,從而使緩存機制通用化。 cachedFetch
的增強功能包括在進行網絡請求之前處理來自會話存儲的緩存命中,以及管理內容過期以避免使用過時數據。 此時,您應該熟悉fetch。它是瀏覽器中一個新的原生API,用於替換舊的XMLHttpRequest API。
Can I Use fetch? https://www.php.cn/link/b751ea087892ebeca363034301f45c69網站上關於主要瀏覽器對fetch功能支持的數據。
在並非所有瀏覽器都完美實現的地方,您可以使用GitHub的fetch polyfill(如果您整天無所事事,這裡有Fetch標準規範)。
假設您確切知道需要下載哪個資源,並且只想下載一次。您可以使用全局變量作為緩存,如下所示:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
這僅僅依賴於全局變量來保存緩存的數據。直接的問題是,如果您重新加載頁面或導航到新頁面,緩存的數據就會消失。
在我們剖析其缺點之前,讓我們升級一下第一個簡單的解決方案。
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
第一個直接的問題是fetch是基於Promise的,這意味著我們無法確定它何時完成,因此為了確定起見,我們不應依賴於它的執行,直到它的Promise解析。
第二個問題是此解決方案非常特定於特定的URL和特定的緩存數據片段(在此示例中為關鍵信息)。我們想要的是一個基於URL的通用解決方案。
讓我們圍繞fetch創建一個包裝器,它也返回一個Promise。調用它的代碼可能並不關心結果是來自網絡還是來自本地緩存。
所以想像一下您曾經這樣做:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
現在您想對其進行包裝,以便重複的網絡調用可以從本地緩存中獲益。讓我們簡單地將其稱為cachedFetch,因此代碼如下所示:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
第一次運行時,它需要通過網絡解析請求並將結果存儲在緩存中。第二次應該直接從本地存儲中提取。
讓我們從簡單地包裝fetch函數的代碼開始:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });
這可以工作,但當然沒用。讓我們首先實現存儲提取的數據。
cachedFetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { console.log('您的来源是 ' + info.origin); });
這裡有很多事情要做。
fetch返回的第一個Promise實際上會繼續執行GET請求。如果CORS(跨源資源共享)有問題,.text()、.json()或.blob()方法將無法工作。
最有趣的功能是,我們必須克隆第一個Promise返回的Response對象。如果我們不這樣做,我們就會過度注入自己,當Promise的最終用戶嘗試調用.json()(例如)時,他們會收到此錯誤:
const cachedFetch = (url, options) => { return fetch(url, options); };
需要注意的另一件事是對響應類型的仔細處理:我們只在狀態碼為200 並且內容類型為application/json或text/*時才存儲響應。這是因為sessionStorage只能存儲文本。
以下是如何使用它的示例:
const cachedFetch = (url, options) => { // 使用URL作为sessionStorage的缓存键 let cacheKey = url; return fetch(url, options).then(response => { // 让我们只在内容类型为JSON或非二进制内容时存储在缓存中 let ct = response.headers.get('Content-Type'); if (ct && (ct.match(/application\/json/i) || ct.match(/text\//i))) { // 有一个.json()而不是.text(),但我们将它存储在sessionStorage中作为字符串。 // 如果我们不克隆响应,它将在返回时被使用。这样我们就可以不干扰。 response.clone().text().then(content => { sessionStorage.setItem(cacheKey, content); }); } return response; }); };
到目前為止,這個解決方案的巧妙之處在於它可以工作,而且不會干擾JSON和HTML請求。當它是圖像時,它不會嘗試將其存儲在sessionStorage中。
因此,我們的第一次實現只是負責存儲請求的響應。但是,如果您第二次調用cachedFetch,它仍然不會嘗試從sessionStorage檢索任何內容。我們需要做的首先是返回一個Promise,並且Promise需要解析一個Response對象。
讓我們從一個非常基本的實現開始:
<code>TypeError: Body has already been consumed.</code>
它可以工作!
要查看它的實際效果,請打開此代碼的CodePen,然後在開發者工具中打開瀏覽器的“網絡”選項卡。按幾次“運行”按鈕(CodePen的右上角),您應該會看到只有圖像正在重複通過網絡請求。
此解決方案的一個巧妙之處在於缺乏“回調意大利面”。由於sessionStorage.getItem調用是同步的(即阻塞的),我們不必在Promise或回調中處理“它是否在本地存儲中?”。並且只有在有內容的情況下,我們才會返回緩存的結果。如果沒有,if語句只會繼續執行常規代碼。
到目前為止,我們一直在使用sessionStorage,它就像localStorage一樣,只是sessionStorage在您啟動新選項卡時會被清除。這意味著我們正在利用一種“自然方式”來避免緩存時間過長。如果我們改用localStorage並緩存某些內容,即使遠程內容已更改,它也會永遠卡在那裡,這很糟糕。
更好的解決方案是讓用戶控制。 (在這種情況下,用戶是使用我們的cachedFetch函數的Web開發人員)。就像服務器端的Memcached或Redis存儲一樣,您可以設置一個生存期,指定應緩存多長時間。
例如,在Python(使用Flask)中:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
現在,sessionStorage和localStorage都沒有內置此功能,因此我們必須手動實現它。我們將通過始終記錄存儲時間的時間戳來做到這一點,並使用它來比較可能的緩存命中。
但在我們這樣做之前,它會是什麼樣子?比如這樣:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(info => { sessionStorage.setItem('information', JSON.stringify(info)); }); // 需要延迟以确保fetch已完成 setTimeout(() => { let info = JSON.parse(sessionStorage.getItem('information')); console.log('您的来源是 ' + info.origin); }, 3000);
我們將添加的關鍵新內容是,每次保存響應數據時,我們也會記錄何時存儲它。但請注意,現在我們也可以切換到localStorage的更可靠存儲,而不是sessionStorage。我們的自定義過期代碼將確保我們不會在持久性localStorage中獲得非常陳舊的緩存命中。
所以這是我們最終的工作解決方案:
fetch('https://httpbin.org/get') .then(r => r.json()) .then(issues => { console.log('您的来源是 ' + info.origin); });
我們不僅避免了過度訪問這些Web API,最好的部分是localStorage比依賴網絡快得多。請參閱這篇博文以了解localStorage與XHR的比較:localForage vs. XHR。它測量其他內容,但基本上得出結論,localStorage非常快,磁盤緩存預熱很少見。
那麼我們如何進一步改進我們的解決方案呢?
我們這裡的實現不會緩存非文本內容(如圖像),但沒有理由不能緩存。我們需要更多代碼。特別是,我們可能想要存儲更多關於Blob的信息。每個響應基本上都是一個Blob。對於文本和JSON,它只是一個字符串數組。類型和大小並不重要,因為您可以從字符串本身推斷出來。對於二進制內容,blob必須轉換為ArrayBuffer。
對於好奇的人,要查看支持圖像的實現擴展,請查看此CodePen:[https://www.php.cn/link/946af3555203afdb63e571b873e419f6]。
另一個潛在的改進是通過對每個URL(我們用作鍵)進行哈希處理來用空間換取速度,使其變得更小。在上面的示例中,我們只使用了一些非常小巧簡潔的URL(例如https://httpbin.org/get),但是如果您有非常長的URL,有很多查詢字符串內容,並且有很多這樣的URL,那麼它們加起來就會非常多。
解決這個問題的方法是使用這種巧妙的算法,它被認為是安全且快速的:
let origin = null; fetch('https://httpbin.org/get') .then(r => r.json()) .then(information => { origin = information.origin; // 您的客户端IP }); // 需要延迟以确保fetch已完成 setTimeout(() => { console.log('您的来源是 ' + origin); }, 3000);
如果您喜歡這個,請查看此CodePen:[https://www.php.cn/link/946af3555203afdb63e571b873e419f6]。如果您在Web控制台中檢查存儲,您會看到類似於557027443的鍵。
您現在有一個可以添加到Web應用程序中的工作解決方案,在該解決方案中,您可能正在使用Web API,並且您知道響應可以很好地為您的用戶緩存。
最後一件事可能是此原型的自然擴展,即將其超越文章,進入一個真實的、具體的項目,帶有測試和自述文件,並在npm上發布它——但這留待以後再說!
緩存已提取的AJAX請求對於提高Web應用程序的性能至關重要。它允許瀏覽器存儲服務器響應的副本,以便它不必再次發出相同的請求。這減少了服務器的負載,並加快了網頁的加載時間,從而提供了更好的用戶體驗。
Fetch API提供了一種強大且靈活的方法來發出HTTP請求。它包含一個內置的緩存機制,允許您指定請求應如何與緩存交互。您可以將緩存模式設置為“default”、“no-store”、“reload”、“no-cache”、“force-cache”或“only-if-cached”,每種模式都提供不同級別的緩存控制。
Fetch API提供了幾種緩存模式。 “default”遵循標準的HTTP緩存規則。 “no-store”完全繞過緩存。 “reload”忽略任何緩存數據並發送新的請求。 “no-cache”在使用緩存版本之前使用服務器驗證數據。 “force-cache”無論其新鮮度如何都使用緩存數據。 “only-if-cached”僅在緩存數據可用時才使用它,否則失敗。
您可以通過在AJAX設置中設置cache屬性來在AJAX請求中實現緩存。如果設置為true,它將允許瀏覽器緩存響應。或者,您可以使用Fetch API的緩存選項來更好地控制緩存的行為。
要防止AJAX請求中的緩存,您可以將AJAX設置中的cache屬性設置為false。這將強制瀏覽器不將其響應存儲在其緩存中。或者,您可以使用Fetch API的“no-store”緩存選項來完全繞過緩存。
雖然AJAX和Fetch API都提供了緩存機制,但Fetch API提供了更大的靈活性和控制性。 AJAX的cache屬性是一個簡單的布爾值,它允許或不允許緩存。另一方面,Fetch API的緩存選項允許您指定請求應如何與緩存交互,從而為您提供更細粒度的控制。
緩存可以顯著提高Web應用程序的性能。通過存儲服務器響應的副本,瀏覽器不必再次發出相同的請求。這減少了服務器的負載,並加快了網頁的加載時間。但是,必須正確管理緩存,以確保您的用戶看到最新的內容。
是的,您可以通過為每個請求在AJAX設置中設置cache屬性來控制單個AJAX請求的緩存行為。這允許您指定瀏覽器是否應該緩存響應。
清除AJAX請求的緩存可以通過在AJAX設置中將cache屬性設置為false來完成。這將強制瀏覽器不將其響應存儲在其緩存中。或者,您可以使用Fetch API的“reload”緩存選項來忽略任何緩存數據並發送新的請求。
緩存AJAX請求的一些最佳實踐包括:了解不同的緩存模式以及何時使用它們,正確管理緩存以確保用戶看到最新的內容,以及使用Fetch API的緩存選項來更好地控制緩存。在決定緩存策略時,還必須考慮數據的性質和用戶體驗。
以上是緩存在本地提取Ajax請求:包裝Fetch API的詳細內容。更多資訊請關注PHP中文網其他相關文章!