indexedDB事件功用的Promise化封装

媒介

本文是引见我在编写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的写法都去掉。

    原文作者:WoodenSail
    原文地址: https://segmentfault.com/a/1190000003982058
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞