资讯专栏INFORMATION COLUMN

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

JackJiang / 2658人阅读

摘要:在不可以用的前提下,无论是同步化或者链式操作都用不了。于是昨天我自己实现了一个简单的同步执行的,并以此为基础实现了链式操作。

前言

本来这个系列应该不会这么快更新,然而昨晚写完前一篇后才发现我的思路中有一个巨大的漏洞。导致我在前一篇中花费大量时间实现了一个复杂的Transaction类——其实完全有更简单的方式来完成这一切。
前篇:http://segmentfault.com/a/1190000003982058
项目地址:https://github.com/woodensail/indexedDB

昨天的疏忽

昨天在设计封装库时,一开始是打算利用《co模块的前端实现》中实现的async库来完成数据库访问的同步化。然而尝试之后就发现并不可行,问题在前篇中提到过,出在Promise的机制上。于是得出结论:indexedDB不可以用原生Promise进行封装。
在不可以用Promise的前提下,无论是同步化或者链式操作都用不了。于是昨天我自己实现了一个简单的同步执行的Promise,并以此为基础实现了链式操作。
我在昨天的文章写完之后才突然想到,既然没有Promise就没法实现同步化链式操作,那么我自己实现完Promise后完全可以不用使用链式操作,直接一步到位实现同步化。所以这篇文章就是关于如何完成indexedDB同步化封装。

使用样例

这是一段最基础的用法,依然和昨天一样实现了get,put,getKV,putKV,四个函数。
这种写法下每个操作是多带带事务的,无法保证原子性。

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手动清除默认事务。

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

这是一个不完全版的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中,会在后面介绍

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等函数的封装。

// 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数据库来用了,以后我会进一步添加更多的方法。各位亲们,推荐或者收藏一下呗

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

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

相关文章

  • indexedDB事务功能Promise封装

    摘要:综上,对进行一定的封装,来简化编码操作。化的尝试对于这种带大量回调的,使用进行异步化封装是个好主意。因此包括在内的所有异步方法都会强制中止当前事务。这就决定了一个事务内部的所有操作必须是同步完成的。目前只实现了和,其他的有待下一步工作。 前言 本文是介绍我在编写indexedDB封装库中诞生的一个副产品——如何让indexedDB在支持链式调用的同时,保持对事务的支持。项目地址:htt...

    zombieda 评论0 收藏0
  • 基于promise对象两种类库q库和bluebird基础调用和原理

    摘要:上一节讲述了的原理及实现,这一节为大家带来两个基于封装的库,方便我们在项目实战中能够方便采用处理异步。的方法,也是生成一个成功的对象,但是确是将的参数传入对象成功回调中作为成功回调参数。对象和的处理方式和调用方式相同。 上一节讲述了promise的原理及实现,这一节为大家带来两个基于promise封装的库,方便我们在项目实战中能够方便采用promise处理异步。 一、q库 www.n...

    Travis 评论0 收藏0
  • 深入探析koa之异步回调处理篇

    摘要:而之后,我们得到的是一个是一个对象,我们可以使用语句定义回调函数,函数的内容呢,则是将读取到的返回给并继续让从断点处执行。 在上一篇中我们梳理了koa当中中间件的洋葱模型执行原理,并实现了一个可以让洋葱模型自动跑起来的流程管理函数。这一篇,我们再来研究一下koa当中异步回调同步化写法的原理,同样的,我们也会实现一个管理函数,是的我们能够通过同步化的写法来写异步回调函数。 1. 回调金字...

    Drinkey 评论0 收藏0
  • IndexedDB 打造靠谱 Web 离线数据库

    摘要:设置为参数设置指定索引,并确保唯一性上面主要做了件事打开数据库表新建,并设置设置打开数据库表主要就是版本号和名字,没有太多讲的,我们直接从创建开始吧。打开注意事项检查是否支持版本更新在生成一个实例时,需要手动指定一个版本号。 在知乎和我在平常工作中,常常会看到一个问题: 前端现在还火吗? 这个我只想说: 隔岸观火的人永远无法明白起火的原因,只有置身风暴,才能找到风眼之所在 ——『秦时明...

    孙吉亮 评论0 收藏0
  • Understanding PWA

    摘要:安装事件绑定在文件中,当安装成功后,事件就会被触发。激活当安装完成后并进入激活状态,会触发事件。这会导致更新得不到响应。由两个构成用来显示系统的通知用来处理下发的消息这两个都是建立在在基础上的,在后台响应推送消息时间,并把他们传递给应用。 showImg(https://segmentfault.com/img/bVbhbQf?w=1182&h=656); 原文地址: https://...

    EsgynChina 评论0 收藏0

发表评论

0条评论

JackJiang

|高级讲师

TA的文章

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