资讯专栏INFORMATION COLUMN

indexedDB事务功能的Promise化封装

zombieda / 2033人阅读

摘要:综上,对进行一定的封装,来简化编码操作。化的尝试对于这种带大量回调的,使用进行异步化封装是个好主意。因此包括在内的所有异步方法都会强制中止当前事务。这就决定了一个事务内部的所有操作必须是同步完成的。目前只实现了和,其他的有待下一步工作。

前言

本文是介绍我在编写indexedDB封装库中诞生的一个副产品——如何让indexedDB在支持链式调用的同时,保持对事务的支持。
项目地址:https://github.com/woodensail/indexedDB

2015/11/12 注:这篇文章的思路有问题,大家看看了解一下就行,不要这么干。更好的做法已经写在了下一篇中。大家可以去看一下,顺便帮忙点个推荐或者收藏一个。
地址:http://segmentfault.com/a/1190000003984871

indexedDB的基本用法
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然后返回就需要这样:

// 方案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进行一定的封装,来简化编码操作。

Promise化的尝试

对于这种带大量回调的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)
});

看上去这么做是没有问题的,但是实质上,在存储第二条数据时,会报错并提示事务已被停止。

事务与Promise的冲突

When control is returned to the event loop, the implementation MUST set the active flag to false.

——摘自W3C推荐标准(W3C Recommendation 08 January 2015)

如同上面的引用所说,目前的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的写法都去掉。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86187.html

相关文章

  • indexedDB事务功能Promise封装(二)——利用generator完成同步改造

    摘要:在不可以用的前提下,无论是同步化或者链式操作都用不了。于是昨天我自己实现了一个简单的同步执行的,并以此为基础实现了链式操作。 前言 本来这个系列应该不会这么快更新,然而昨晚写完前一篇后才发现我的思路中有一个巨大的漏洞。导致我在前一篇中花费大量时间实现了一个复杂的Transaction类——其实完全有更简单的方式来完成这一切。前篇:http://segmentfault.com/a/11...

    JackJiang 评论0 收藏0
  • html5 indexeddb简明api

    摘要:网上搜来一堆,,几乎没有找到满意的答案,经过汇总并结合自己的理解,封装了一套简单的是一个异步对象,必须使用回调函数方式进行调用打开一个数据库,支持两个参数,第二个参数指定版本号,我没用到,让浏览器自己创建版本号。 网上搜来一堆api,demo,几乎没有找到满意的答案,经过汇总并结合自己的理解,封装了一套简单的api // indexedDB是一个异步对象,必须使用回调函数方式进行调用 ...

    luckyw 评论0 收藏0
  • IndexedDB 简单封装

    摘要:之前我在开发过程中使用的是,可以直接写查询数据。,用键值模式存储数据,而且就是专门为小数量数据设计的。只能是字符串而且空间有限。下面是自己看了阮一峰的文章简单的学习了下对这个浏览器数据库有个大概的了解,下面是个人对简单的封装。IndexedDB 浏览器数据库,是一个非关系型数据库,数据形式使用的是json,IndexedDB适合存储大量数据,它的API是异步调用的,当然他的api 也相对复杂...

    Songlcy 评论0 收藏0
  • 【译】渐进式 Web App 离线存储

    摘要:离线存储数据的建议对寻址资源,使用这是的一部分。在到达储量限制之前,两种存储机制都会一直进行存储。则没有对存储量做出限制,只是在之后会弹出提醒。是异步的基于回调函数,但它同样不支持。也是异步的基于回调函数,在和中可以工作虽然使用的是同步。 拖拖拉拉好久,终于把个人博客整出来了。鸣谢 @pinggod。 厚着脸安利一下,地址是 http://www.wemlion.com/。欢迎访问,欢...

    Joyven 评论0 收藏0
  • 【译】渐进式 Web App 离线存储

    摘要:离线存储数据的建议对寻址资源,使用这是的一部分。在到达储量限制之前,两种存储机制都会一直进行存储。则没有对存储量做出限制,只是在之后会弹出提醒。是异步的基于回调函数,但它同样不支持。也是异步的基于回调函数,在和中可以工作虽然使用的是同步。 拖拖拉拉好久,终于把个人博客整出来了。鸣谢 @pinggod。 厚着脸安利一下,地址是 http://www.wemlion.com/。欢迎访问,欢...

    Charlie_Jade 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<