vue3 专用 indexedDB 封装库,基于Promise告别回调地狱(一)

简介: https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API 这个大概是官网吧,原始是英文的,现在陆续是出中文版。有空的话还是多看看官网。

IndexedDB 的官网

https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API 这个大概是官网吧,原始是英文的,现在陆续是出中文版。有空的话还是多看看官网。

简介

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 可以使用索引实现对数据的高性能搜索。

简单的说就是 —— 能装!indexedDB 是前端的一种“事务型对象数据库”,可以存放很多很多的对象(当然也可以是其他类型),功能很强大,可以用作数据(对象)在前端的缓存容器。或者其他用途。

使用也是很简单的,网上可以找到很多教程,官网也推荐了几个封装类库。只是我比较懒,得看别人的类库(好吧,我看不懂),而是想按照自己的想法封装一个自己用着习惯的类库。

最近在使用 Vue3,所以想针对 Vue3 做一套 indexedDB 的类库,实现客户端缓存数据的功能。

其实这里介绍的应该算是第二版了,第一版在项目里面试用一段时间后,发现了几个问题,所以想在新版本里面一起解决。

indexedDB 的操作思路

一开始看 indexedDB 的时候各种懵逼,只会看大神写的文章,然后照猫画虎,先不管原理,把代码copy过来能运行起来,能读写数据即可。

现在用了一段时间,有了一点理解,整理如下:

53.png


使用思路

  • 获取 indexedDB 的对象
  • open (打开/建立)数据库。
  • 如果没有数据库,或者版本升级:
  • 调用 onupgradeneeded(建立/修改数据库),然后调用 onsuccess。
  • 如果已有数据库,且版本不变,那么直接调用 onsuccess。
  • 在 onsuccess 里得到连接对象后:
  • 开启事务。
  • 得到对象仓库。
  • 执行各种操作:添加、修改、删除、获取等。
  • 用索引和游标实现查询。
  • 得到结果

思路明确之后,我们就好封装了。

做一个 help,封装初始化的代码

前端数据库和后端数据库对比一下,就会发现一个很明显的区别,后端数据库是先配置好数据库,建立需要的表,然后添加初始数据,最后才开始运行项目。

在项目里面不用考虑数据库是否已经建立好了,直接用就行。

但是前端数据库就不行了,必须先考虑数据库有没有建立好,初始数据有没有添加进去,然后才可以开始常规的操作。

所以第一步就是要封装一下初始化数据库的部分。

我们先建立一个 help.js 文件,在里面写一个 ES6 的 class。

/**
 * indexedDB 的 help,基础功能的封装
 * * 打开数据库,建立对象仓库,获取连接对象,实现增删改查
 * * info 的结构:
 * * * dbFlag: '' // 数据库标识,区别不同的数据库
 * * * dbConfig: { // 连接数据库
 * * * * dbName: '数据库名称',
 * * * * ver: '数据库版本',
 * * * },
 * * * stores: {
 * * * * storeName: { // 对象仓库名称
 * * * * * id: 'id', // 主键名称
 * * * * * index: { // 可以不设置索引
 * * * * * * name: ture, // key:索引名称;value:是否可以重复
 * * * * * }
 * * * * }
 * * * },
 * * * init: (help) => {} // 完全准备好之后的回调函数
 */
exportdefaultclass IndexedDBHelp {
  constructor (info) {
    this.myIndexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
    if (!this.myIndexedDB) {
      console.log('您的浏览器不支持IndexedDB')
    }
    // 数据库名称和版本号
    this._info = {
      dbName: info.dbConfig.dbName,
      ver: info.dbConfig.ver
    }
    // 记录连接数据库的对象, IDBDatabase 类型,因为open是异步操作,所以不能立即获得。
    this._db = null
    // 记录仓库状态。new:新库或者版本升级后;old:有对象仓库了。
    this._storeState = 'pending'
    /**
     * 注册回调事件。
     * * 如果组件读写 indexedDB 的时还没有准备好的话,
     * * 可以来注册一个事件,等准备好了之后回调。
     */
    this._regCallback = []
    // 打开数据库,异步操作,大概需要几毫秒的时间。
    this.dbRequest = this.myIndexedDB.open(this._info.dbName, this._info.ver)
    // 第一次,或者版本升级时执行,根据配置信息建立表
    this.dbRequest.onupgradeneeded = (event) => {
      this._storeState = 'new'
      const db = event.target.result
      // console.log('【2】新建或者升级数据库 onupgradeneeded --- ', db)
      for (const key in info.stores) {
        const store = info.stores[key]
        if (db.objectStoreNames.contains(key)) {
          // 已经有仓库,验证一下是否需要删除原来的仓库
          if (store.isClear) {
            // 删除原对象仓库,没有保存数据
            db.deleteObjectStore(key)
            // 建立新对象仓库
            const objectStore = db.createObjectStore(key, { keyPath: store.id })
            // 建立索引
            for (const key2 in store.index) {
              const unique = store.index[key2]
              objectStore.createIndex(key2, key2, { unique: unique })
            }
          }
        } else {
          // 没有对象仓库,建立
          const objectStore = db.createObjectStore(key, { keyPath: store.id }) /* 自动创建主键 autoIncrement: true */
          // 建立索引
          for (const key2 in store.index) {
            const unique = store.index[key2]
            objectStore.createIndex(key2, key2, { unique: unique })
          }
        }
      }
    }
    // 数据库打开成功,记录连接对象
    this.dbRequest.onsuccess = (event) => {
      this._db = event.target.result // dbRequest.result
      // console.log('【1】成功打开数据库 onsuccess --- ', this._db)
      // 修改状态
      if (this._storeState === 'pending') {
        this._storeState = 'old'
      }
      // 调用初始化的回调
      if (typeof info.init === 'function') {
        info.init(this)
      }
      // 调用组件注册的回调
      this._regCallback.forEach(fn => {
        if (typeof fn === 'function') {
          fn()
        }
      })
    }
    // 处理出错信息
    this.dbRequest.onerror = (event) => {
      // 出错
      console.log('打开数据库出错:', event.target.error)
    }
  }
  // 挂载其他操作,后面介绍。。。
}


这里要做几个主要的事情:

  • 判断浏览器是否支持 indexedDB
  • 打开数据库
  • 设置对象仓库
  • 保存连接对象,备用

另外使用 jsDoc 进行参数说明,有的时候是可以出现提示,就算不出提示,也是可以有说明的作用,避免过几天自己都想不起来怎么用参数了。

54.png


参数提示

挂载事务

拿到数据库的连接对象之后,我们可以(必须)开启一个事务,然后才能执行其他操作。

所以我们需要先把事务封装一下,那么为啥要单独封装事务呢?

因为这样可以实现打开一个事务,然后传递事务实例,从而实现连续操作的目的,虽然这种的情况不是太多,但是感觉还是应该支持一下这种功能。

begin-tran.js

/**
 * 开启一个读写的事务
 * @param {*} help indexedDB 的 help
 * @param {Array} storeName 字符串的数组,对象仓库的名称
 * @param {string} type readwrite:读写事务;readonly:只读事务;versionchange:允许执行任何操作,包括删除和创建对象存储和索引。
 * @returns 读写事务
 */
const beginTran = (help, storeName, type = 'readwrite') => {
  returnnewPromise((resolve, reject) => {
    const _tran = () => {
      const tranRequest = help._db.transaction(storeName, type)
      tranRequest.onerror = (event) => {
        console.log(type + ' 事务出错:', event.target.error)
        reject(`${type} 事务出错:${event.target.error}`)
      }
      resolve(tranRequest)
      tranRequest.oncomplete = (event) => {
        // console.log('beginReadonly 事务完毕:', window.performance.now())
      }
    }
    if (help._db) {
      _tran() // 执行事务
    } else {
      // 注册一个回调事件
      help._regCallback.push(() => _tran())
    }
  })
}
exportdefault beginTran
  • 支持多个对象仓库 storeName  是字符串数组,所以可以针对多个对象仓库同时开启事务,然后通过 tranRequest.objectStore(storeName) 来获取具体的对象仓库。
  • 挂载到 help 因为是写在单独的js文件里面,所以还需要在 help 里面引入这个js文件,然后挂到 help 上面,以便实现 help.xxx 的调用形式,这样拿到help即可,不用 import 各种函数了。
import _beginTran from'./begin-tran.js'// 事务
... help 的其他代码
  // 读写的事务
  beginWrite (storeName) {
    return _beginTran(this, storeName, 'readwrite')
  }
  // 只读的事务
  beginReadonly (storeName) {
    return _beginTran(this, storeName, 'readonly')
  }

是不是有一种“循环调用”的感觉?js 就是可以这么放飞自我。然后需要我们写代码的时候就要万分小心,因为不小心的话很容易写出来死循环。

挂载增删改查

事务准备好了,我们就可以进行下一步操作。

先设计一个添加对象的 js文件:

addModel.js

import _vueToObject from'./_toObject.js'
/**
 * 添加对象
 * @param { IndexedDBHelp } help 访问数据库的实例
 * @param { string } storeName 仓库名称(表名)
 * @param { Object } model 对象
 * @param { IDBTransaction } tranRequest 如果使用事务的话,需要传递开启事务时创建的连接对象
 * @returns 新对象的ID
 */
exportdefaultfunction addModel (help, storeName, model, tranRequest = null) {
  // 取对象的原型,便于保存 reactive 
  const _model = _vueToObject(model)
  // 定义一个 Promise 的实例
  returnnewPromise((resolve, reject) => {
    // 定义个函数,便于调用
    const _add = (__tran) => {
      __tran
        .objectStore(storeName) // 获取store
        .add(_model) // 添加对象
        .onsuccess = (event) => { // 成功后的回调
          resolve(event.target.result) // 返回对象的ID
        }
    }
    if (tranRequest === null) {
      help.beginWrite([storeName]).then((tran) => {
        // 自己开一个事务
        _add(tran)
      })
    } else {
      // 使用传递过来的事务
      _add(tranRequest)
    }
  })
}

首先使用 Promise 封装默认的回调模式,然后可以传递进来一个事务进来,这样可以实现打开事务连续添加的功能。

如果不传递事务的话,内部会自己开启一个事务,这样添加单个对象的时候也会很方便。

然后在 help 里面引入这个 js文件,再设置一个函数:

import _addModel from'./model-add.js'// 添加一个对象
  /**
   * 添加一个对象
   * @param {string} storeName 仓库名称
   * @param {object} model 要添加的对象
   * @param {*} tranRequest 事务,可以为null
   * @returns 
   */
  addModel (storeName, model, tranRequest = null) {
    return _addModel(this, storeName, model, tranRequest = null)
  }

这样就可以挂载上来了。把代码分在多个 js文件里面,便于维护和扩展。

55.png

使用提示

修改、删除和获取的代码也类似,就不一一列举了。

相关文章
|
11月前
|
NoSQL JavaScript 前端开发
毕设专用 基于Vue的大病保险管理系统 这个开源项目你值得拥有(二)
毕设专用 基于Vue的大病保险管理系统 这个开源项目你值得拥有
|
1月前
|
前端开发 JavaScript
js开发:请解释Promise是什么,以及它如何解决回调地狱(callback hell)问题。
Promise是JavaScript解决异步操作回调地狱的工具,代表未来可能完成的值。传统的回调函数嵌套导致代码难以维护,而Promise通过链式调用`.then()`和`.catch()`使异步流程清晰扁平。每个异步操作封装为Promise,成功时`.then()`传递结果,出错时`.catch()`捕获异常。ES6的`async/await`进一步简化Promise的使用,使异步代码更接近同步风格。
37 1
|
10月前
|
前端开发 小程序
Promise封装Axios进行高效开发
Promise封装Axios进行高效开发
|
1月前
|
消息中间件 前端开发 JavaScript
Promise:使用Promise,告别回调函数
Promise:使用Promise,告别回调函数
42 1
Promise:使用Promise,告别回调函数
|
1月前
|
前端开发 JavaScript
vue组件制作专题 - (mpvue专用)在mpvue中手写css实现简单左右轮播
vue组件制作专题 - (mpvue专用)在mpvue中手写css实现简单左右轮播
27 0
|
1月前
|
前端开发 JavaScript
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
用原生JavaScript(ES5)来实现Promise的等效功能(异步回调)
|
1月前
|
前端开发 API
用promise封装ajax
用promise封装ajax
29 0
|
7月前
|
前端开发 JavaScript API
Promise封装Ajax请求
Promise封装Ajax请求
32 0
|
7月前
|
JavaScript 前端开发 API
vue项目中配置简单的代理与promise,并简单封装请求接口
vue项目中配置简单的代理与promise,并简单封装请求接口
34 0
|
7月前
|
小程序 前端开发 API
小程序api封装 promise使用
小程序api封装 promise使用
38 0