摘要:譯前端離線指南上原文鏈接作者緩存持久化為您的站點(diǎn)提供一定量的可用空間來(lái)執(zhí)行其所需的操作。這是可能的,因?yàn)橥ǔ?huì)保持使內(nèi)容最具線性特性的順序。
[[譯]前端離線指南(上)](https://juejin.im/post/5c0788...緩存持久化原文鏈接:The offline cookbook 作者:Jake Archibald
為您的站點(diǎn)提供一定量的可用空間來(lái)執(zhí)行其所需的操作。該可用空間可在站點(diǎn)中所有存儲(chǔ)之間共享:LocalStorage、IndexedDB、Filesystem,當(dāng)然也包含Caches。
您能獲取到的空間容量是不一定的,同時(shí)由于設(shè)備和存儲(chǔ)條件的差異也會(huì)有所不同。您可以通過(guò)下面的代碼來(lái)查看您已獲得的空間容量:
navigator.storageQuota.queryInfo("temporary").then((info) => { console.log(info.quota); // Result:console.log(info.usage); // Result: });
然而,與所有瀏覽器存儲(chǔ)一樣,如果設(shè)備面臨存儲(chǔ)壓力,瀏覽器就會(huì)隨時(shí)舍棄這些存儲(chǔ)內(nèi)容。但遺憾的是,瀏覽器無(wú)法區(qū)分您珍藏的電影,和您沒(méi)啥興趣的游戲之間有啥區(qū)別。
為解決此問(wèn)題,建議使用 requestPersistent API:
// 在頁(yè)面中運(yùn)行 navigator.storage.requestPersistent().then((granted) => { if (granted) { // 啊哈,數(shù)據(jù)保存在這里呢 } });
當(dāng)然,用戶必須要授予權(quán)限。讓用戶參與進(jìn)這個(gè)流程是很有必要的,因?yàn)槲覀兛梢灶A(yù)期用戶會(huì)控制刪除。如果用戶手中的設(shè)備面臨存儲(chǔ)壓力,而且清除不重要的數(shù)據(jù)還沒(méi)能解決問(wèn)題,那么用戶就需要根據(jù)自己的判斷來(lái)決定刪除哪些項(xiàng)目以及保留哪些項(xiàng)目。
為了實(shí)現(xiàn)此目的,需要操作系統(tǒng)將“持久化”源等同于其存儲(chǔ)使用空間細(xì)分中的本機(jī)應(yīng)用,而不是作為單個(gè)項(xiàng)目報(bào)告給瀏覽器。
緩存建議-響應(yīng)請(qǐng)求無(wú)論您打算緩存多少內(nèi)容,除非您告訴ServiceWorker應(yīng)當(dāng)在何時(shí)以及如何去緩存內(nèi)容,ServiceWorker不會(huì)去主動(dòng)使用緩存。下面是幾種用于處理請(qǐng)求的策略。
僅緩存(cache only)適用于: 您認(rèn)為在站點(diǎn)的“該版本”中屬于靜態(tài)內(nèi)容的任何資源。您應(yīng)當(dāng)在install事件中就緩存這些資源,以便您可以在處理請(qǐng)求的時(shí)候依靠它們。
self.addEventListener("fetch", (event) => { // 如果某個(gè)匹配到的資源在緩存中找不到, // 則響應(yīng)結(jié)果看起來(lái)就會(huì)像一個(gè)連接錯(cuò)誤。 event.respondWith(caches.match(event.request)); });
...盡管您一般不需要通過(guò)特殊的方式來(lái)處理這種情況,但“緩存,回退到網(wǎng)絡(luò)”涵蓋了這種策略。
僅網(wǎng)絡(luò)(network only)
適用于: 沒(méi)有相應(yīng)的離線資源的對(duì)象,比如analytics pings,非GET請(qǐng)求。
self.addEventListener("fetch", (event) => { event.respondWith(fetch(event.request)); // 或者簡(jiǎn)單地不再調(diào)用event.respondWith,這樣就會(huì) // 導(dǎo)致默認(rèn)的瀏覽器行為 });
...盡管您一般不需要通過(guò)特殊的方式來(lái)處理這種情況,但“緩存,回退到網(wǎng)絡(luò)”涵蓋了這種策略。
緩存優(yōu)先,若無(wú)緩存則回退到網(wǎng)絡(luò)(Cache, falling back to network)
適用于: 如果您以緩存優(yōu)先的方式構(gòu)建,那么這種策略就是您處理大多數(shù)請(qǐng)求時(shí)所用的策略 根據(jù)傳入請(qǐng)求而定,其他策略會(huì)有例外。
self.addEventListener("fetch", (event) => { event.respondWith(async function() { const response = await caches.match(event.request); return response || fetch(event.request); }()); });
其中,針對(duì)已緩存的資源提供“Cache only”的行為,針對(duì)未緩存的資源(包含所有非GET請(qǐng)求,因?yàn)樗鼈兏緹o(wú)法被緩存)提供“Network only”的行為。
緩存與網(wǎng)絡(luò)競(jìng)爭(zhēng)
適用于: 存儲(chǔ)在讀取速度慢的硬盤中的小型資源。
在老舊硬盤、病毒掃描程序、和較快網(wǎng)速這幾種因素都存在的情況下,從網(wǎng)絡(luò)中獲取資源可能比從硬盤中獲取的速度更快。不過(guò),通過(guò)網(wǎng)絡(luò)獲取已經(jīng)在用戶設(shè)備中保存過(guò)的內(nèi)容,是一種浪費(fèi)流量的行為,所以請(qǐng)牢記這一點(diǎn)。
// Promise.race 對(duì)我們來(lái)說(shuō)并不太好,因?yàn)槿舢?dāng)其中一個(gè)promise在 // fulfilling之前reject了,那么整個(gè)Promise.race就會(huì)返回reject。 // 我們來(lái)寫一個(gè)更好的race函數(shù): function promiseAny(promises) { return new Promise((resolve, reject) => { // 確保promises代表所有的promise對(duì)象。 promises = promises.map(p => Promise.resolve(p)); // 只要當(dāng)其中一個(gè)promise對(duì)象調(diào)用了resolve,就讓此promise對(duì)象變成resolve的 promises.forEach(p => p.then(resolve)); // 如果傳入的所有promise都reject了,就讓此promise對(duì)象變成resject的 promises.reduce((a, b) => a.catch(() => b)) .catch(() => reject(Error("All failed"))); }); }; self.addEventListener("fetch", (event) => { event.respondWith( promiseAny([ caches.match(event.request), fetch(event.request) ]) ); });通過(guò)網(wǎng)絡(luò)獲取失敗回退到緩存(Network falling back to cache)
適用于: 對(duì)頻繁更新的資源進(jìn)行快速修復(fù)。例如:文章、頭像、社交媒體時(shí)間軸、游戲排行榜等。
這就意味著您可以為在線用戶提供最新內(nèi)容,但是離線用戶獲取到的是較老的緩存版本。如果網(wǎng)絡(luò)請(qǐng)求成功,您可能需要更新緩存。
不過(guò),這種方法存在缺陷。如果用戶的網(wǎng)絡(luò)斷斷續(xù)續(xù),或者網(wǎng)速超慢,則用戶可能會(huì)在從自己設(shè)備中獲取更好的、可接受的內(nèi)容之前,花很長(zhǎng)一段時(shí)間去等待網(wǎng)絡(luò)請(qǐng)求失敗。這樣的用戶體驗(yàn)是非常糟糕的。請(qǐng)查看下一個(gè)更好的解決方案:“緩存然后訪問(wèn)網(wǎng)絡(luò)”。
self.addEventListener("fetch", (event) => { event.respondWith(async function() { try { return await fetch(event.request); } catch (err) { return caches.match(event.request); } }()); });緩存然后訪問(wèn)網(wǎng)絡(luò)
適用于: 更新頻繁的內(nèi)容。例如:文章、社交媒體時(shí)間軸、游戲排行榜等。
這種策略需要頁(yè)面發(fā)起兩個(gè)請(qǐng)求,一個(gè)是請(qǐng)求緩存,一個(gè)是請(qǐng)求網(wǎng)絡(luò)。首先展示緩存數(shù)據(jù),然后當(dāng)網(wǎng)絡(luò)數(shù)據(jù)到達(dá)的時(shí)候,更新頁(yè)面。
有時(shí)候,您可以在獲取到新的數(shù)據(jù)的時(shí)候,只替換當(dāng)前數(shù)據(jù)(比如:游戲排行榜),但是具有較大的內(nèi)容時(shí)將導(dǎo)致數(shù)據(jù)中斷?;旧现v,不要在用戶可能正在閱讀或正在操作的內(nèi)容突然“消失”。
Twitter在舊內(nèi)容上添加新內(nèi)容,并調(diào)整滾動(dòng)的位置,以便讓用戶感知不到。這是可能的,因?yàn)?Twitter 通常會(huì)保持使內(nèi)容最具線性特性的順序。 我為 trained-to-thrill 復(fù)制了此模式,以盡快獲取屏幕上的內(nèi)容,但當(dāng)它出現(xiàn)時(shí)仍會(huì)顯示最新內(nèi)容。
頁(yè)面中的代碼
async function update() { // 盡可能地發(fā)起網(wǎng)絡(luò)請(qǐng)求 const networkPromise = fetch("/data.json"); startSpinner(); const cachedResponse = await caches.match("/data.json"); if (cachedResponse) await displayUpdate(cachedResponse); try { const networkResponse = await networkPromise; const cache = await caches.open("mysite-dynamic"); cache.put("/data.json", networkResponse.clone()); await displayUpdate(networkResponse); } catch (err) { } stopSpinner(); const networkResponse = await networkPromise; } async function displayUpdate(response) { const data = await response.json(); updatePage(data); }常規(guī)回退
如果您未能從網(wǎng)絡(luò)和緩存中提供某些資源,您可能需要一個(gè)常規(guī)回退策略。
適用于: 次要的圖片,比如頭像,失敗的POST請(qǐng)求,“離線時(shí)不可用”的頁(yè)面。
self.addEventListener("fetch", (event) => { event.respondWith(async function() { // 嘗試從緩存中匹配 const cachedResponse = await caches.match(event.request); if (cachedResponse) return cachedResponse; try { // 回退到網(wǎng)絡(luò) return await fetch(event.request); } catch (err) { // 如果都失敗了,啟用常規(guī)回退: return caches.match("/offline.html"); // 不過(guò),事實(shí)上您需要根據(jù)URL和Headers,準(zhǔn)備多個(gè)不同回退方案 // 例如:頭像的兜底圖 } }()); });
您回退到的項(xiàng)目可能是一個(gè)“安裝依賴項(xiàng)”(見(jiàn)《前端離線指南(上)》中的“安裝時(shí)——以依賴的形式”小節(jié))。
ServiceWorker-side templating
適用于: 無(wú)法緩存其服務(wù)器響應(yīng)的頁(yè)面。
在服務(wù)器上渲染頁(yè)面可提高速度,但這意味著會(huì)包括在緩存中沒(méi)有意義的狀態(tài)數(shù)據(jù),例如,“Logged in as…”。如果您的頁(yè)面由 ServiceWorker 控制,您可能會(huì)轉(zhuǎn)而選擇請(qǐng)求 JSON 數(shù)據(jù)和一個(gè)模板,并進(jìn)行渲染。
importScripts("templating-engine.js"); self.addEventListener("fetch", (event) => { const requestURL = new URL(event.request); event.responseWith(async function() { const [template, data] = await Promise.all([ caches.match("/article-template.html").then(r => r.text()), caches.match(requestURL.path + ".json").then(r => r.json()), ]); return new Response(renderTemplate(template, data), { headers: {"Content-Type": "text/html"} }) }()); });總結(jié)
您不必只選擇其中的一種方法,您可以根據(jù)請(qǐng)求URL選擇使用多種方法。比如,在trained-to-thrill中使用了:
在安裝時(shí)緩存,適用于靜態(tài)UI
在網(wǎng)絡(luò)響應(yīng)時(shí)緩存,適用于網(wǎng)絡(luò)圖片和數(shù)據(jù)
從緩存中獲取,若失敗回退到網(wǎng)絡(luò),適用于大部分的請(qǐng)求
從緩存中獲取,然后請(qǐng)求網(wǎng)絡(luò),適用于網(wǎng)絡(luò)搜索結(jié)果
只需要根據(jù)請(qǐng)求,就能決定要做什么:
self.addEventListener("fetch", (event) => { // Parse the URL: const requestURL = new URL(event.request.url); // Handle requests to a particular host specifically if (requestURL.hostname == "api.example.com") { event.respondWith(/* some combination of patterns */); return; } // Routing for local URLs if (requestURL.origin == location.origin) { // Handle article URLs if (/^/article//.test(requestURL.pathname)) { event.respondWith(/* some other combination of patterns */); return; } if (requestURL.pathname.endsWith(".webp")) { event.respondWith(/* some other combination of patterns */); return; } if (request.method == "POST") { event.respondWith(/* some other combination of patterns */); return; } if (/cheese/.test(requestURL.pathname)) { event.respondWith( new Response("Flagrant cheese error", { status: 512 }) ); return; } } // A sensible default pattern event.respondWith(async function() { const cachedResponse = await caches.match(event.request); return cachedResponse || fetch(event.request); }()); });鳴謝
感謝下列諸君為本文提供那些可愛(ài)的圖標(biāo):
Code,作者:buzzyrobot
Calendar,作者:Scott Lewis
Network,作者:Ben Rizzo
SD,作者:Thomas Le Bas
CPU,作者:iconsmind.com
Trash,作者:trasnik
Notification,作者:@daosme
Layout,作者:Mister Pixel
Cloud,作者:P.J. Onori
同時(shí)感謝 Jeff Posnick 在我點(diǎn)擊“發(fā)布”按鈕之前,為我找到多處明顯錯(cuò)誤。
擴(kuò)展閱讀Intro to ServiceWorkers
Is ServiceWorker ready? - 跟蹤主流瀏覽器對(duì)ServiceWorker的實(shí)現(xiàn)狀態(tài)
JavaScript promises, there and back again - Promise指南
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/99813.html
摘要:接受一個(gè)對(duì)象作參數(shù),來(lái)定義安裝時(shí)長(zhǎng)和安裝是否成功,如果狀態(tài)為,則認(rèn)為此次安裝失敗,并且拋棄如果一個(gè)舊版本的正在運(yùn)行,則它將保持不變。在頁(yè)面既可以在中獲取到,也可以在頁(yè)面中獲取到,這就意味著你不必一定要通過(guò)來(lái)向緩存中添加內(nèi)容。 原文鏈接:The offline cookbook作者:Jake Archibald 使用AppCache可以為我們提供幾種支持內(nèi)容離線工作的模式。如果這些模式正...
摘要:雖然有著各種各樣的不同,但是相同的是,他們前端優(yōu)化不完全指南前端掘金篇幅可能有點(diǎn)長(zhǎng),我想先聊一聊閱讀的方式,我希望你閱讀的時(shí)候,能夠把我當(dāng)作你的競(jìng)爭(zhēng)對(duì)手,你的夢(mèng)想是超越我。 如何提升頁(yè)面渲染效率 - 前端 - 掘金Web頁(yè)面的性能 我們每天都會(huì)瀏覽很多的Web頁(yè)面,使用很多基于Web的應(yīng)用。這些站點(diǎn)看起來(lái)既不一樣,用途也都各有不同,有在線視頻,Social Media,新聞,郵件客戶端...
摘要:是在谷歌的年開(kāi)發(fā)者峰會(huì)上宣布,但穩(wěn)定的技術(shù)和工具終于在月到達(dá)。固然也不能保證蘋果將實(shí)施這項(xiàng)技術(shù),但這并不重要,你的應(yīng)用程序仍然可以在中工作,它只是不會(huì)從離線執(zhí)行中受益。我有一種感覺(jué)一旦上體驗(yàn)有明顯提升蘋果將鼓勵(lì)支持。 2016年是值得紀(jì)念、奇怪的、有點(diǎn)歡騰/可怕的一年,取決于你的觀點(diǎn)。跟其他事件相比僅僅專注于JavaScript可能看起來(lái)無(wú)關(guān)緊要,但它是每個(gè)Web開(kāi)發(fā)人員的工作生活中巨...
摘要:是在谷歌的年開(kāi)發(fā)者峰會(huì)上宣布,但穩(wěn)定的技術(shù)和工具終于在月到達(dá)。固然也不能保證蘋果將實(shí)施這項(xiàng)技術(shù),但這并不重要,你的應(yīng)用程序仍然可以在中工作,它只是不會(huì)從離線執(zhí)行中受益。我有一種感覺(jué)一旦上體驗(yàn)有明顯提升蘋果將鼓勵(lì)支持。 2016年是值得紀(jì)念、奇怪的、有點(diǎn)歡騰/可怕的一年,取決于你的觀點(diǎn)。跟其他事件相比僅僅專注于JavaScript可能看起來(lái)無(wú)關(guān)緊要,但它是每個(gè)Web開(kāi)發(fā)人員的工作生活中巨...
摘要:是在谷歌的年開(kāi)發(fā)者峰會(huì)上宣布,但穩(wěn)定的技術(shù)和工具終于在月到達(dá)。固然也不能保證蘋果將實(shí)施這項(xiàng)技術(shù),但這并不重要,你的應(yīng)用程序仍然可以在中工作,它只是不會(huì)從離線執(zhí)行中受益。我有一種感覺(jué)一旦上體驗(yàn)有明顯提升蘋果將鼓勵(lì)支持。 2016年是值得紀(jì)念、奇怪的、有點(diǎn)歡騰/可怕的一年,取決于你的觀點(diǎn)。跟其他事件相比僅僅專注于JavaScript可能看起來(lái)無(wú)關(guān)緊要,但它是每個(gè)Web開(kāi)發(fā)人員的工作生活中巨...
閱讀 6450·2021-11-22 15:32
閱讀 938·2021-11-11 16:54
閱讀 3251·2021-10-13 09:40
閱讀 2249·2021-09-03 10:35
閱讀 1921·2021-08-09 13:47
閱讀 1942·2019-08-30 15:55
閱讀 2001·2019-08-30 15:43
閱讀 2518·2019-08-29 17:06