LocalForage - indexDB driver代码解析

一、 indexDB简介

indexDB是为了解决h5的localStorage在容量,存储类型上的限制而提出的web客户端数据库,与localStorage最大的不同在于其更多的从数据库的角度对API进行了设计,已在safari7.1, IE10,chrome23以上的版本中得到实现。该API规范提出了以下对象

  • 数据库:IDBDatabase 对象
  • 对象仓库:IDBObjectStore 对象
  • 索引: IDBIndex 对象
  • 事务: IDBTransaction 对象
  • 操作请求:IDBRequest 对象
  • 指针: IDBCursor 对象
  • 主键集合:IDBKeyRange 对象

indexDB的工作逻辑参照下图所示(实线同步,虚线异步,下同):
  1. 首先通过indexDB.open指定名称和版本打开数据库,获取IDBRequst对象,后续过程为异步
  2. 如果版本号增加则会发生onupgradeneeded事件,否则仅发生success事件,可在该事件中通过request.result或e.target.result获取到IDBDatabase对象,即数据库本身;
  3. 获取数据库后,可通过objectStoreName.contains检查数据库存储的表,如果没有需要查询的表可通过createObjectStore新建表,新建时如不指明keyPath则默认为id字段递增;如果存在可以直接通过objectStore获取指定的IDBObjectStore表对象;
  4. 获取表后,可通过createIndex建立索引;
  5. 如需对表进行增删改查操作,必须借由事务API进行,需要调用db.transcation(storeNames,operationType)获取指定表序列执行指定操作的IDBTransaction对象,operationType目前仅有readwrite和readonly两种;
  6. 获取事务对象后,先通过objectStore指定操作的表,然后通过add/delete/put/get执行相应的增删改查操作,该操作为异步,只可以通过注册success和error的事件监听函数执行回调。

二、代码解析

以下内容为对indexDB驱动相关代码的流程分析,以及相关兼容代码的原理,可供indexDB开发者作为参考,localForage结合promise对于indexDB做了一系列的异步事务调度管理策略,确保多forage实例,多数据库,多objectStorage表可协同工作,不会引发数据库一致性问题。

1. blob兼容

blob兼容主要分为两种情况:

  1. blob文件能否存入indexDB (chrome < 37 || android < 5不支持)
  2. blob文件从indexDB取出使用是否会出现问题 (chrome 37~42 存在解码问题)

相关参考信息:

  • content-type bug: https://code.google.com/p/chromium/issues/detail?id=408120
  • 404 bug: https://code.google.com/p/chromium/issues/detail?id=447916
  • FileReader bug: https://code.google.com/p/chromium/issues/detail?id=447836

localForage的兼容方案如下图所示:

  1. 针对第一种情况,localForage在每次建立新的db时都会专门新建一个名为local-forage-detect-blob-support的表(额外建表为防止调用iterate或length等接口产生干扰)来尝试存储一个blob文件,若该过程失败则直接返回false,缓存到supportsBlobs变量中
  2. 针对第二种情况,在事务的oncomplete事件中检查浏览器版本,如果为chrome 43以上版本或edge(edge接入谷歌内核后需单独在userAgent里check)或其他浏览器,则可完美支持,返回ture,否则返回false,缓存到supportsBlobs
  3. 之后检查文件类型支持时只需访问supportsBlobs本地变量即可

对于无法完美支持blob存储indexDB的情况, localForage采用的向下兼容策略为:

  1. 使用FileReader和btoa将二进制文件转换为base64二进制字符串进行存储,并添加一个转换的标记
  2. 取用时通过_isEncodedBlob似有方法检查是否为转码字符串
  3. 如果是转码字符串,则再将其转换回binary buffer数组
  4. 通过createBlob方法重建二进制文件(构造器优先级为: Blob > BloblBuilder > MSBlobBuilder > MozBlobBuilder > WebKitBlobBuilder, 使用Builder时需通过getBlob接口s获取指定type的Blob文件)

完整流程如下图所示:

2. 数据库驱动

localForage对于indexDB数据库驱动较为复杂,主要原因为兼容多forage同时操作多个db数据库,因而需处理复杂的数据库upgradeneeded事件,并对每个数据库的事务队列进行同步化的管理,完整流程如下图所示:

Step1 实例初始化

  1. 通过_initStorage方法创建forage存储实例,该实例对应指定数据库(name)的指定数据表(storeName),每个实例根据option的设置(name,version,storeName等)建立一个对应的本地记录_dbInfo
  2. 根据这个实例的name属性获取对应的数据库上下文dbContext,若未发现该数据库上下文则通过createDbContext新建一个,forages存储其关联的存储实例,db为对应的数据库连接(初始化为null, dbReady为事务链 , deferredOperations保存延时执行的操作,除初始化和销毁事务链外较少使用
  3. 获取到数据库上下文后,根据options.version分析该数据库的版本是否存在且最新,根据options.storeName判断该数据库是否存在用户指定的数据表,上述条件有一个不符合,则更新数据库连接
  4. 之后, 通过getConnection方法获取数据库连接,如无需升级则直接从dbContext.db中获取,如需升级,则关闭已有连接,增加数据库版本,添加指定的数据表,触发数据库onupgradeneed事件,在其回调中获取数据库连接,并同步到dbContext和dbInfo中

Step2 事务处理

localForage主要使用promise链来管理事务,核心代码如下:

function  _deferReadiness(dbInfo) {
  var dbContext = dbContexts[dbInfo.name];
  // Create a deferred object representing the current database operation.
  var deferredOperation = {};
  deferredOperation.promise = new  Promise(function(resolve, reject) {
    deferredOperation.resolve = resolve;
    deferredOperation.reject = reject;
  });
  // Enqueue the deferred operation.
  dbContext.deferredOperations.push(deferredOperation);
  // Chain its promise to the database readiness.
  if (!dbContext.dbReady) {`
     dbContext.dbReady = deferredOperation.promise;
  } else {
     dbContext.dbReady = dbContext.dbReady.then(function() {
       return deferredOperation.promise;
     });
  }
}

通过这部分代码可以看到,在数据库初始化时,会建立一个事务链的promise对象保存到dbReady里,之后所有新的事务都注册到dbReady的then方法中,并在其resolve时替换链尾,实现循环调用,这一设计十分巧妙。

与初始化时的场景类似,处理事务时也需要照顾到数据库版本更新的问题,同时还需要考虑到更新的数据表不在数据库(被先前的其他实例删掉)的异常情况,如存在问题,则同样需触发数据库升级,并通过tryReconnect重新建立连接,同步dbContext, dbInfo,初始化事务链,再进行操作事务的注册。

Step3 数据库销毁

所对应流程图右下角的内容,主要为通过indexDB.deleteDatabase方法对特定数据库进行删除,同时通过_advanceReadiness或_rejectReadiness预先执行事务链尾promise的回调(视运行时状态而定),从而清空事务链,完成整个销毁过程。

你可能感兴趣的:(LocalForage - indexDB driver代码解析)