原本这个系列应该不会这么快更新,然而昨晚写完前一篇后才发现个人思路中有一个巨大的漏洞。致使我在前一篇中花费大量时间实现了一个复杂的Transaction类——其实彻底有更简单的方式来完成这一切。
前篇:http://segmentfault.com/a/1190000003982058
项目地址:https://github.com/woodensail/indexedDB前端
昨天在设计封装库时,一开始是打算利用《co模块的前端实现》中实现的async库来完成数据库访问的同步化。然而尝试以后就发现并不可行,问题在前篇中提到过,出在Promise的机制上。因而得出结论:indexedDB不能够用原生Promise进行封装。
在不能够用Promise的前提下,不管是同步化或者链式操做都用不了。因而昨天我本身实现了一个简单的同步执行的Promise,并以此为基础实现了链式操做。
我在昨天的文章写完以后才忽然想到,既然没有Promise就无法实现同步化和链式操做,那么我本身实现完Promise后彻底能够不用使用链式操做,直接一步到位实现同步化。因此这篇文章就是关于如何完成indexedDB同步化封装。git
这是一段最基础的用法,依然和昨天同样实现了get,put,getKV,putKV,四个函数。
这种写法下每一个操做是单独事务的,没法保证原子性。github
async(function* () { var db = yield new DB('test'); yield db.put('info', {k: 'async', v: 1}); var result1 = yield db.get('info', 'async'); console.log(result1.v); yield db.putKv('info', 'async', '1'); var result2 = yield db.getKv('info', 'async'); console.log(result2); }).catch(function (e) { console.log(e); });
首先打开一个事务而后做为参数传递给数据库访问函数便可启用事务支持。
下面这两个操做是在同一个事务中的。数据库
async(function* () { var db = yield new DB('test'); var tx = db.transaction('info', 'readwrite'); yield db.put('info', {k: 'async', v: 1}, tx); var result1 = yield db.get('info', 'async', tx); console.log(result1.v); }).catch(function (e) { console.log(e); });
在开启事务时,第三个参数填true,能够将当前事务做为默认事务。后面的操做将自动使用该事务。须要在使用完毕后调用transactionEnd手动清除默认事务。segmentfault
async(function* () { var db = yield new DB('test'); db.transaction('info', 'readwrite', true); yield db.put('info', {k: 'async', v: 1}); var result1 = yield db.get('info', 'async'); console.log(result1.v); db.transactionEnd(); }).catch(function (e) { console.log(e); });
这是一个不彻底版的Promise实现,只提供了最基本的then和catch以及他们的链式调用。反正也够async用了。
暂时没有提供对Promise.all的支持,我估计要支持得修改async库才行,因此就之后再说吧。缓存
var DbPromise = function (fun) { this.state = 'pending'; this.resolveList = []; this.rejectList = []; var _this = this; fun(function () { _this.resolve.apply(_this, arguments) }, function () { _this.reject.apply(_this, arguments) }); }; DbPromise.prototype = {};
DbPromise.prototype.resolve = function (data) { this.state = 'resolved'; this.data = data; var _this = this; this.resolveList.forEach(function (fun) { _this.data = fun(_this.data) }); }; DbPromise.prototype.reject = function (data) { this.state = 'rejected'; this.error = data; this.rejectList.forEach(function (fun) { fun(data); }); };
DbPromise.prototype.then = function (fun) { if (this.state === 'pending') { this.resolveList.push(fun); } else { this.data = fun(this.data); } return this; }; DbPromise.prototype.catch = function (fun) { if (this.state === 'pending') { this.rejectList.push(fun); } else { fun(this.error); } return this; };
定义class DB,打开数据库的操做定义在_open中,会在后面介绍app
var DB = function (name, upgrade, version) { var _this = this; // 能够在其余js文件中经过向DB.dbMap添加成员的方式来预约义数据库 // 若是是已经预约义过的数据库就能够调用new DB(name)来打开 // 不然须要调用new DB(name, upgrade, version),填写upgrade响应函数和版本号 if (DB.dbMap[name]) { var map = DB.dbMap[name]; return _open(name, map.upgrade, map.version).then(function (db) { _this.db = db; return _this; }).then(map.nextStep); } else { return _open(name, upgrade, version).then(function (db) { _this.db = db; return _this; }); } }; DB.prototype = {};
打开事务和取消关闭事务的方法。异步
DB.prototype.transaction = function (table, type, asDefault) { // 根据给定的目标和类型打开当前数据库的事务 var tx = _transaction(this.db, table, type); // 若是asDefault为真,则缓存该事务将其做为默认事务。 // 注意目前同时设置多个默认事务是会冲突的, // 这种状况理论上有极小的可能性在异步操做中出现,我会在接下来的版本中改正这一点。 if (asDefault) { this.tx = tx; } return tx; }; //取消默认事务 DB.prototype.transactionEnd = function () { this.tx = void 0; }; function _transaction(db, table, type) { return db.transaction(table, type); }
具体的数据库操做函数。实际上是对_put等函数的封装。async
// tx || this.tx 指的是优先使用参数指定的事务,其次使用默认事务 DB.prototype.put = function (table, data, tx) { return _put(this.db, table, data, tx || this.tx); }; DB.prototype.get = function (table, name, tx) { return _get(this.db, table, name, tx || this.tx); }; DB.prototype.clear = function (table, tx) { return _clear(this.db, table, tx || this.tx); }; //这两个是对get和put的特殊封装,多了参数和结果的预处理,简化了参数和返回值的格式 DB.prototype.getKv = function (table, k, tx) { return _get(this.db, table, k, tx).then(o=>(o || {}).v); }; DB.prototype.putKv = function (table, k, v, tx) { return _put(this.db, table, {k, v}, tx); };
_open,_put,_get,_clear的实现因为后三者相似,因此只贴了_put。须要后两点代码请查看github。函数
function _open(name, upgrade, version) { // 返回自定义Promise供async库调用 return new DbPromise(function (resolve, reject) { // 打开指定数据库的指定版本 var request = indexedDB.open(name, version); // 设置升级操做 request.onupgradeneeded = upgrade; // 绑定success和error,其中成功后会返回打开的数据库对象 request.onsuccess = function (e) { resolve(request.result); }; request.onerror = reject; }); }
function _put(db, table, data, tx) { // 返回自定义Promise供async库调用 return new DbPromise(function (resolve, reject) { // 若是没有提供事务则建立新事务 tx = tx || db.transaction(table, 'readwrite'); // 打开store并进行操做 var store = tx.objectStore(table); var request = store.put(data); // 设置回调 request.onsuccess = function () { resolve(request.result); }; request.onerror = reject; }); }
基本上,在实现了DbPromise以后其余部分的实现方式就是按老一套来就好了,很是的简单。我昨天只是光棍节脑壳抽筋没反应过来而已。
如今的库已经能够当作基本的Key-Value数据库来用了,之后我会进一步添加更多的方法。各位亲们,推荐或者收藏一下呗。