本文是介绍我在编写indexedDB封装库中诞生的一个副产品——如何让indexedDB在支持链式调用的同时,保持对事务的支持。
项目地址:https://github.com/woodensail/indexedDBgit
2015/11/12 注:这篇文章的思路有问题,你们看看了解一下就行,不要这么干。更好的作法已经写在了下一篇中。你们能够去看一下,顺便帮忙点个推荐或者收藏一个。
地址:http://segmentfault.com/a/1190000003984871es6
var tx = db.transaction('info', 'readonly'); var store = tx.objectStore('info'); store.get('id').onsuccess = function (e) { console.log(e.target.result); };
上面这段代码中,开启了一个事务,并从名为info的store中取得了key为id的记录,并打印在控制台。
其中打印的部分写在了onsuccess回调中,若是咱们但愿把取出的id加1而后返回就须要这样:github
// 方案1 var tx = db.transaction('info', 'readwrite'); var store = tx.objectStore('info'); store.get('id').onsuccess = function (e) { store.put({key:'id',value:e.target.result.value + 1}).onsuccess = function (e) { …… }; }; // 方案2 var tx = db.transaction('info', 'readwrite'); var store = tx.objectStore('info'); var step2 = function(e){ store.put({key:'id',value:e.target.result.value + 1}).onsuccess = function (e) { …… }; } store.get('id').onsuccess = step2;
前者用到了嵌套回调,后者则须要将业务流程拆散。
综上,对indexedDB进行必定的封装,来简化编码操做。chrome
对于这种带大量回调的API,使用Promise进行异步化封装是个好主意。
咱们能够作以下封装:数据库
function put(db, table, data ,tx) { return new Promise(function (resolve) { var store = tx.objectStore(table); store.put(data).onsuccess = function (e) { resolve(e); }; }); } var tx = db.transaction('info', 'readwrite'); Promise.resolve().then(function(){ put(db, 'info', {……}, tx) }).then(function(){ put(db, 'info', {……}, tx) });
看上去这么作是没有问题的,可是实质上,在存储第二条数据时,会报错并提示事务已被中止。segmentfault
When control is returned to the event loop, the implementation MUST set the active flag to false.数组
——摘自W3C推荐标准(W3C Recommendation 08 January 2015)promise
如同上面的引用所说,目前的W3C标准要求在控制权回到事件循环时,当前开启的事务必须被设置为关闭。所以包括Promise.then在内的全部异步方法都会强制停止当前事务。这就决定了一个事务内部的全部操做必须是同步完成的。
也真是基于这个缘由,我没有在github上找到实现链式调用的indexedDB封装库。
其中寸志前辈的BarnJS中到是有链式调用,然而只是实现了Event.get().then()。也就是只能一次数据库操做,一次结果处理,而后就结束。并不能串联多个数据库操做在同一个事务内。缓存
不够要实现链式调用其实也不难,关键的问题就在于Promise自己是为异步操做而生的,所以会在链式调用的各个函数中返回事件循环,从而减小网页的卡顿。因此咱们就须要实现一个在执行每一个函数过程之间不会返回事件循环的Promise,也就是一个同步化的Promise。异步
也许是这个要求太过奇葩,我没发现网上有提供同步化执行的promise库。因此只能本身实现一个简单的。虽然功能不全,但也能凑活用了。下面是使用样例和详细代码解释,完整代码见github。
// 这句是我封装事后的用法,等效于: // var tx = new Transaction(db, 'info', 'readwrite'); var tx = dbObj.transaction('info', 'readwrite'); //正常写法 tx.then(function () { tx.get('info', 'a'); tx.get('info', 'b'); }).then(function (a, b) { tx.put('info', {key : 'c', value : Math.max(a.v, b.v)); }) //偷懒写法 tx.then(function () { tx.getKV('info', 'a'); tx.getKV('info', 'b'); }).then(function (a, b) { tx.putKV('info', 'c', Math.max(a, b)); })
var Transaction = function (db, table, type) { this.transaction = db.transaction(table, type); this.requests = []; this.nexts = []; this.errorFuns = []; }; Transaction.prototype.then = function (fun) { var _this = this; // 若errored为真则视为已经出错,直接返回。此时后面的then语句都被放弃。 if (this.errored) { return this; } // 若是当前队列为空则将自身入队后,马上执行,不然只入队,不执行。 if (!_this.nexts.length) { _this.nexts.push(fun); fun(_this.results); _this.goNext(); } else { _this.nexts.push(fun); } // 返回this以实现链式调用 return _this; };
Transaction的初始化语句和供使用者调用的then语句。
Transaction.prototype.put = function (table, data) { var store = this.transaction.objectStore(table); this.requests.push([store.put(data)]); }; Transaction.prototype.get = function (table, key) { var store = this.transaction.objectStore(table); this.requests.push([store.get(key)]); }; Transaction.prototype.putKV = function (table, k, v) { var store = this.transaction.objectStore(table); this.requests.push([store.put({k, v})]); }; Transaction.prototype.getKV = function (table, key) { var store = this.transaction.objectStore(table); this.requests.push([store.get(key), item=>(item || {}).v]); };
全部的函数都在发起数据库操做后将返回的request对象暂存入this.requests中。
目前只实现了put和get,其余的有待下一步工做。另外,getKV和setKV是专门用于存取key-value数据的,要求被存取的store包含k,v两个字段,其中k为主键。
// 该语句会在链式调用中的每一个函数被执行后马上调用,用于处理结果,并调用队列中的下一个函数。 Transaction.prototype.goNext = function () { var _this = this; // 统计前一个函数块中执行的数据库操做数量 var total = _this.requests.length; // 清空已完成数据库操做计数器 _this.counter = 0; // 定义所有操做执行完毕或出差后的回调函数 var success = function () { // 当已经有错误出现时,放弃下一步执行 if (_this.errored) { return; } // 将队首的节点删除,也就是刚刚执行完毕的节点 _this.nexts.shift(); _this.requests = []; // 从返回的event集合中提取出全部result,若是有parser则使用parser。 _this.results = _this.events.map(function (e, index) { if (_this.parser[index]) { return _this.parser[index](e.target.result); } else { return e.target.result; } }); //判断队列是否已经执行完毕,不然继续执行下一个节点 if (_this.nexts.length) { // 将节点的执行结果做为参数传给下一个节点,使用了spread操做符。 _this.nexts[0](..._this.results); _this.goNext(); } }; // 初始化events数组,清空parser存储器 _this.events = new Array(total); _this.parser = {}; // 若该请求内不包含数据库操做,则视为已完成,直接调用success if (total === 0) { success(); } // 对于每一个请求将请求附带的parser放入存储区。而后绑定onsuccess和onerror。 // 其中onsuccess会在每一个请求成功后将计数器加一,当计数器等于total时执行回调 _this.requests.forEach(function (request, index) { _this.parser[index] = request[1]; request[0].onsuccess = _this.onsuccess(total, index, success); request[0].onerror = _this.onerror; }) };
Transaction.prototype.onsuccess = function (total, index, callback) { var _this = this; return function (e) { // 将返回的event存入event集合中的对应位置 _this.events[index] = e; _this.counter++; if (_this.counter === total) { callback(); } } }; Transaction.prototype.onerror = function (e) { // 设置错误标准 this.errored = true; // 保存报错的event this.errorEvent = e; // 一次调用全部已缓存的catch函数 this.errorFuns.forEach(fun=>fun(e)); }; Transaction.prototype.cache = function (fun) { // 若是已设置错误标准则用缓存的event为参数马上调用fun,不然将其存入队列中 if (this.errored) { fun(this.errorEvent); } else { this.errorFuns.push(fun); } };
核心的goNext语句以及success与error的回调。catch相似then用于捕捉异常。
好累啊,就这样吧,之后再加其余功能吧。另外这里面用了很多es6的写法。因此请务必使用最新版的edge或chrome或firefox运行。或者你能够手动把es6的写法都去掉。