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

前言

原本這個系列應該不會這麼快更新,然而昨晚寫完前一篇後才發現個人思路中有一個巨大的漏洞。致使我在前一篇中花費大量時間實現了一個複雜的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

這是一個不徹底版的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數據庫來用了,之後我會進一步添加更多的方法。各位親們,推薦或者收藏一下唄

相關文章
相關標籤/搜索