IndexedDB瀏覽器內(nèi)建數(shù)據(jù)庫(kù)并行更新問題詳解
正文
IndexedDB 是一個(gè)瀏覽器內(nèi)建的數(shù)據(jù)庫(kù),它比 localStorage
強(qiáng)大得多。
- 通過支持多種類型的鍵,來存儲(chǔ)幾乎可以是任何類型的值。
- 支撐事務(wù)的可靠性。
- 支持鍵值范圍查詢、索引。
- 和
localStorage
相比,它可以存儲(chǔ)更大的數(shù)據(jù)量。
對(duì)于傳統(tǒng)的 客戶端-服務(wù)器 應(yīng)用,這些功能通常是沒有必要的。IndexedDB 適用于離線應(yīng)用,可與 ServiceWorkers 和其他技術(shù)相結(jié)合使用。
根據(jù)規(guī)范 www.w3.org/TR/IndexedD… 中的描述,IndexedDB 的本機(jī)接口是基于事件的。
我們還可以在基于 promise 的包裝器(wrapper),如 github.com/jakearchiba… 的幫助下使用 async/await
。這要方便的多,但是包裝器并不完美,它并不能替代所有情況下的事件。因此,我們先練習(xí)事件(events),在理解了 IndexedDB 之后,我們將使用包裝器。
數(shù)據(jù)在哪兒?
從技術(shù)上講,數(shù)據(jù)通常與瀏覽器設(shè)置、擴(kuò)展程序等一起存儲(chǔ)在訪問者的主目錄中。
不同的瀏覽器和操作系統(tǒng)級(jí)別的用戶都有各自獨(dú)立的存儲(chǔ)。
打開數(shù)據(jù)庫(kù)
要想使用 IndexedDB,首先需要 open
(連接)一個(gè)數(shù)據(jù)庫(kù)。
語法:
let openRequest = indexedDB.open(name, version);
name
—— 字符串,即數(shù)據(jù)庫(kù)名稱。version
—— 一個(gè)正整數(shù)版本,默認(rèn)為1
(下面解釋)。
數(shù)據(jù)庫(kù)可以有許多不同的名稱,但是必須存在于當(dāng)前的源(域/協(xié)議/端口)中。不同的網(wǎng)站不能相互訪問對(duì)方的數(shù)據(jù)庫(kù)。
調(diào)用之后會(huì)返回 openRequest
對(duì)象,我們需要監(jiān)聽該對(duì)象上的事件:
success
:數(shù)據(jù)庫(kù)準(zhǔn)備就緒,openRequest.result
中有了一個(gè)數(shù)據(jù)庫(kù)對(duì)象“Database Object”,我們應(yīng)該將其用于進(jìn)一步的調(diào)用。error
:打開失敗。upgradeneeded
:數(shù)據(jù)庫(kù)已準(zhǔn)備就緒,但其版本已過時(shí)(見下文)。
IndexedDB 具有內(nèi)建的“模式(scheme)版本控制”機(jī)制,這在服務(wù)器端數(shù)據(jù)庫(kù)中是不存在的。
與服務(wù)器端數(shù)據(jù)庫(kù)不同,IndexedDB 存在于客戶端,數(shù)據(jù)存儲(chǔ)在瀏覽器中。因此,開發(fā)人員無法隨時(shí)都能訪問它。因此,當(dāng)我們發(fā)布了新版本的應(yīng)用程序,用戶訪問我們的網(wǎng)頁(yè),我們可能需要更新該數(shù)據(jù)庫(kù)。
如果本地?cái)?shù)據(jù)庫(kù)版本低于 open
中指定的版本,會(huì)觸發(fā)一個(gè)特殊事件 upgradeneeded
。我們可以根據(jù)需要比較版本并升級(jí)數(shù)據(jù)結(jié)構(gòu)。
當(dāng)數(shù)據(jù)庫(kù)還不存在時(shí)(從技術(shù)上講,其版本為 0
),也會(huì)觸發(fā) upgradeneeded
事件。因此,我們可以執(zhí)行初始化。
假設(shè)我們發(fā)布了應(yīng)用程序的第一個(gè)版本。
接下來我們就可以打開版本 1
中的 IndexedDB 數(shù)據(jù)庫(kù),并在一個(gè) upgradeneeded
的處理程序中執(zhí)行初始化,如下所示:
let openRequest = indexedDB.open("store", 1); openRequest.onupgradeneeded = function() { // 如果客戶端沒有數(shù)據(jù)庫(kù)則觸發(fā) // ...執(zhí)行初始化... }; openRequest.onerror = function() { console.error("Error", openRequest.error); }; openRequest.onsuccess = function() { let db = openRequest.result; // 繼續(xù)使用 db 對(duì)象處理數(shù)據(jù)庫(kù) };
之后不久,我們發(fā)布了第二個(gè)版本。
我們可以打開版本 2
中的 IndexedDB 數(shù)據(jù)庫(kù),并像這樣進(jìn)行升級(jí):
let openRequest = indexedDB.open("store", 2); openRequest.onupgradeneeded = function(event) { // 現(xiàn)有的數(shù)據(jù)庫(kù)版本小于 2(或不存在) let db = openRequest.result; switch(event.oldVersion) { // 現(xiàn)有的 db 版本 case 0: // 版本 0 表示客戶端沒有數(shù)據(jù)庫(kù) // 執(zhí)行初始化 case 1: // 客戶端版本為 1 // 更新 } };
請(qǐng)注意:雖然我們目前的版本是 2
,onupgradeneeded
處理程序有針對(duì)版本 0
的代碼分支(適用于初次訪問,瀏覽器中沒有數(shù)據(jù)庫(kù)的用戶)和針對(duì)版本 1
的代碼分支(用于升級(jí))。
接下來,當(dāng)且僅當(dāng) onupgradeneeded
處理程序沒有錯(cuò)誤地執(zhí)行完成,openRequest.onsuccess
被觸發(fā),數(shù)據(jù)庫(kù)才算是成功打開了。
刪除數(shù)據(jù)庫(kù):
let deleteRequest = indexedDB.deleteDatabase(name) // deleteRequest.onsuccess/onerror 追蹤(tracks)結(jié)果
我們無法使用較舊的 open 調(diào)用版本打開數(shù)據(jù)庫(kù)
如果當(dāng)前用戶的數(shù)據(jù)庫(kù)版本比 open
調(diào)用的版本更高(比如當(dāng)前的數(shù)據(jù)庫(kù)版本為 3
,我們卻嘗試運(yùn)行 open(...2)
,就會(huì)產(chǎn)生錯(cuò)誤并觸發(fā) openRequest.onerror
)。
這很罕見,但這樣的事情可能會(huì)在用戶加載了一個(gè)過時(shí)的 JavaScript 代碼時(shí)發(fā)生(例如用戶從一個(gè)代理緩存中加載 JS)。在這種情況下,代碼是過時(shí)的,但數(shù)據(jù)庫(kù)卻是最新的。
為了避免這樣的錯(cuò)誤產(chǎn)生,我們應(yīng)當(dāng)檢查 db.version
并建議用戶重新加載頁(yè)面。使用正確的 HTTP 緩存頭(header)來避免之前緩存的舊代碼被加載,這樣你就永遠(yuǎn)不會(huì)遇到此類問題。
并行更新問題
提到版本控制,有一個(gè)相關(guān)的小問題。
舉個(gè)例子:
- 一個(gè)用戶在一個(gè)瀏覽器標(biāo)簽頁(yè)中打開了數(shù)據(jù)庫(kù)版本為
1
的我們的網(wǎng)站。 - 接下來我們發(fā)布了一個(gè)更新,使得代碼更新了。
- 接下來同一個(gè)用戶在另一個(gè)瀏覽器標(biāo)簽中打開了這個(gè)網(wǎng)站。
這時(shí),有一個(gè)標(biāo)簽頁(yè)和版本為 1
的數(shù)據(jù)庫(kù)建立了一個(gè)連接,而另一個(gè)標(biāo)簽頁(yè)試圖在其 upgradeneeded
處理程序中將數(shù)據(jù)庫(kù)版本升級(jí)到 2
。
問題是,這兩個(gè)網(wǎng)頁(yè)是同一個(gè)站點(diǎn),同一個(gè)源,共享同一個(gè)數(shù)據(jù)庫(kù)。而數(shù)據(jù)庫(kù)不能同時(shí)為版本 1
和版本 2
。要執(zhí)行版本 2
的更新,必須關(guān)閉對(duì)版本 1
的所有連接,包括第一個(gè)標(biāo)簽頁(yè)中的那個(gè)。
為了解決這一問題,versionchange
事件會(huì)在“過時(shí)的”數(shù)據(jù)庫(kù)對(duì)象上觸發(fā)。我們需要監(jiān)聽這個(gè)事件,關(guān)閉對(duì)舊版本數(shù)據(jù)庫(kù)的連接(還應(yīng)該建議訪問者重新加載頁(yè)面,以加載最新的代碼)。
如果我們不監(jiān)聽 versionchange
事件,也不去關(guān)閉舊連接,那么新的連接就不會(huì)建立。openRequest
對(duì)象會(huì)產(chǎn)生 blocked
事件,而不是 success
事件。因此第二個(gè)標(biāo)簽頁(yè)無法正常工作。
下面是能夠正確處理并行升級(jí)情況的代碼。它安裝了 onversionchange
處理程序,如果當(dāng)前數(shù)據(jù)庫(kù)連接過時(shí)(數(shù)據(jù)庫(kù)版本在其他位置被更新)并關(guān)閉連接,則會(huì)觸發(fā)該處理程序。
let openRequest = indexedDB.open("store", 2); openRequest.onupgradeneeded = ...; openRequest.onerror = ...; openRequest.onsuccess = function() { let db = openRequest.result; db.onversionchange = function() { db.close(); alert("Database is outdated, please reload the page.") }; // ……數(shù)據(jù)庫(kù)已經(jīng)準(zhǔn)備好,請(qǐng)使用它…… }; openRequest.onblocked = function() { // 如果我們正確處理了 onversionchange 事件,這個(gè)事件就不應(yīng)該觸發(fā) // 這意味著還有另一個(gè)指向同一數(shù)據(jù)庫(kù)的連接 // 并且在 db.onversionchange 被觸發(fā)后,該連接沒有被關(guān)閉 };
……換句話說,在這我們做兩件事:
- 如果當(dāng)前數(shù)據(jù)庫(kù)版本過時(shí),
db.onversionchange
監(jiān)聽器會(huì)通知我們并行嘗試更新。 openRequest.onblocked
監(jiān)聽器通知我們相反的情況:在其他地方有一個(gè)與過時(shí)的版本的連接未關(guān)閉,因此無法建立新的連接。
我們可以在 db.onversionchange
中更優(yōu)雅地進(jìn)行處理,提示訪問者在連接關(guān)閉之前保存數(shù)據(jù)等。
或者,另一種方式是不在 db.onversionchange
中關(guān)閉數(shù)據(jù)庫(kù),而是使用 onblocked
處理程序(在瀏覽器新 tab 頁(yè)中)來提醒用戶,告訴他新版本無法加載,直到他們關(guān)閉瀏覽器其他 tab 頁(yè)。
這種更新沖突很少發(fā)生,但我們至少應(yīng)該有一些對(duì)其進(jìn)行處理的程序,至少在 onblocked
處理程序中進(jìn)行處理,以防程序默默卡死而影響用戶體驗(yàn)。
以上就是IndexedDB瀏覽器內(nèi)建數(shù)據(jù)庫(kù)并行更新問題詳解的詳細(xì)內(nèi)容,更多關(guān)于IndexedDB數(shù)據(jù)庫(kù)并行更新的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Navicat快速導(dǎo)入和導(dǎo)出sql文件的方法
Navicat是MySQL非常好用的可視化管理工具,功能非常強(qiáng)大,能滿足我們?nèi)粘?shù)據(jù)庫(kù)開發(fā)的所有需求。今天教大家如何導(dǎo)入和導(dǎo)出SQL文件,感興趣的朋友跟隨小編一起看看吧2021-05-05分布式緩存Redis與Memcached的優(yōu)缺點(diǎn)區(qū)別比較
Redis和Memcached都是基于內(nèi)存key-value的數(shù)據(jù)存儲(chǔ)系統(tǒng)。兩者都可以通過緩存數(shù)據(jù)結(jié)果,HTML片段或其他可能產(chǎn)生成本很高的內(nèi)容來幫助加快應(yīng)用程序的速度。與memcached相比,Redis功能更強(qiáng)大,更受歡迎并且得到更好的支持。2022-12-12Linux的HBASE數(shù)據(jù)庫(kù)集群部署方法
HBase是一種針對(duì)海量數(shù)據(jù)的key-value型NoSQL數(shù)據(jù)庫(kù),本文詳細(xì)介紹了在Linux系統(tǒng)下HBase的安裝與配置步驟,本文給大家介紹Linux的HBASE數(shù)據(jù)庫(kù)集群部署方法,感興趣的朋友一起看看吧2024-10-10OLEDB和ODBC的區(qū)別(優(yōu)缺點(diǎn))
ODBC是一種連接數(shù)據(jù)庫(kù)的開放標(biāo)準(zhǔn),OLEDB(對(duì)象鏈接和嵌入數(shù)據(jù)庫(kù))位于ODBC層與應(yīng)用程序之間. 在你的ASP頁(yè)面里,ADO是位于OLEDB之上的應(yīng)用程序. 你的ADO調(diào)用先被送到OLEDB,然后再交由ODBC處理2012-09-09MySQL與Oracle 差異比較之一數(shù)據(jù)類型
這篇文章主要介紹了MySQL與Oracle 差異比較之一數(shù)據(jù)類型,需要的朋友可以參考下2017-04-04使用 Navicat 創(chuàng)建數(shù)據(jù)庫(kù)并用JDBC連接的操作方法
這篇文章主要介紹了使用 Navicat 創(chuàng)建數(shù)據(jù)庫(kù)并用JDBC連接的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11