Как использовать обещания с IndexedDB без автоматической фиксации транзакций? - PullRequest
0 голосов
/ 23 марта 2020

Есть ли способ использовать IndexedDB с обещаниями и async / await без автоматической фиксации транзакции? Я понимаю, что вы не можете делать такие вещи, как выборка сетевых данных в середине транзакции, но все, что я смог найти в Интернете по этой теме, указывает на то, что IndexedDB все еще должен работать, если вы просто заключите его в обещания.

Тем не менее, в моем тестировании (Firefox 73) я обнаружил, что достаточно просто обернуть метод onsuccess запроса в Promise, чтобы вызвать автоматическую фиксацию транзакции перед выполнением обещания, при этом код работает при использовании необработанного API IndexedDB. Что я могу сделать?

Вот минимальный упрощенный пример моего кода.

const {log, error, trace, assert} = console;

const VERSION = 1;
const OBJ_STORE_NAME = 'test';
const DATA_KEY = 'data';
const META_KEY = 'last-updated';

function open_db(name, version) {
    return new Promise((resolve, reject) => {
        const req = indexedDB.open(name, version);
        req.onerror = reject;
        req.onupgradeneeded = e => {
            const db = e.target.result;
            for (const name of db.objectStoreNames) {db.deleteObjectStore(name);}
            db.createObjectStore(OBJ_STORE_NAME);
        };
        req.onsuccess = e => resolve(e.target.result);
    });
}
function idbreq(objs, method, ...args) {
    return new Promise((resolve, reject) => {
        const req = objs[method](...args);
        req.onsuccess = e => resolve(req.result);
        req.onerror = e => reject(req.error);
    });
}
async function update_db(db) {
    const new_time = (new Date).toISOString();
    const new_data = 42; // simplified for sake of example

    const [old_data, last_time] = await (() => {
        const t = db.transaction([OBJ_STORE_NAME], 'readonly');
        t.onabort = e => error('trans1 abort', e);
        t.onerror = e => error('trans1 error', e);
        t.oncomplete = e => log('trans1 complete', e);
        const obj_store = t.objectStore(OBJ_STORE_NAME);
        return Promise.all([
            idbreq(obj_store, 'get', DATA_KEY),
            idbreq(obj_store, 'get', META_KEY),
        ]);
    })();
    log('fetched data from db');
    // do stuff with data before writing it back

    (async () => {
        log('beginning write callback');
        const t = db.transaction([OBJ_STORE_NAME], 'readwrite');
        t.onabort = e => error('trans2 abort', e);
        t.onerror = e => error('trans2 error', e);
        t.oncomplete = e => log('trans2 complete', e);
        const obj_store = t.objectStore(OBJ_STORE_NAME);
        // This line works when using onsuccess directly, but simply wrapping it in a Promise causes the
        // transaction to autocommit before the rest of the code executes, resulting in an error.
        obj_store.get(META_KEY).onsuccess = ({result: last_time2}) => {
            log('last_time', last_time, 'last_time2', last_time2, 'new_time', new_time);
            // Check if some other transaction updated db in the mean time so we don't overwrite newer data
            if (!last_time2 || last_time2 < new_time) {
                obj_store.put(new_time, META_KEY);
                obj_store.put(new_data, DATA_KEY);
            }
            log('finished write callback');
        };



        // This version of the above code using a Promise wrapper results in an error
        // idbreq(obj_store, 'get', META_KEY).then(last_time2 => {
        //     log('last_time', last_time, 'last_time2', last_time2, 'new_time', new_time);
        //     if (!last_time2 || last_time2 < new_time) {
        //         obj_store.put(new_time, META_KEY);
        //         obj_store.put(new_data, DATA_KEY);
        //     }
        //     log('finished write callback');
        // });



        // Ideally, I'd be able to use await like a civilized person, but the above example
        // shows that IndexedDB breaks when simply using promises, even without await.
        // const last_time2 = await idbreq(obj_store, 'get', META_KEY);
        // log('last_time', last_time, 'last_time2', last_time2, 'new_time', new_time);
        // if (!last_time2 || last_time2 < new_time) {
        //     obj_store.put(new_time, META_KEY);
        //     obj_store.put(new_data, DATA_KEY);
        // }
        // log('finished write callback');
    })();
    return [last_time, new_time];
}

open_db('test').then(update_db).then(([prev, new_]) => log(`updated db timestamp from ${prev} to ${new_}`));

Ответы [ 2 ]

0 голосов
/ 28 марта 2020

Оказывается, проблема была в совершенно другой части моего кода.

В конце моего кода верхнего уровня у меня было

.catch(e => {
    error('caught error', e);
    alert(e);
});

Я не уверен о деталях, но отображение предупреждения, по-видимому, вызывает автоматическую фиксацию всех транзакций, в то время как обещания все еще ожидают выполнения, что приводит к появлению ошибок, которые я видел, когда пользователь нажимает «ок» во всплывающем окне с предупреждением, и ожидающие обещания продолжаются. Удаление вызова alert из моего глобального обработчика ошибок устранило проблему.

0 голосов
/ 23 марта 2020

Обещания Orchestrate вокруг транзакций, а не отдельных запросов.

Если это вызывает проблемы с вашим дизайном, и вы все еще хотите использовать indexedDB, то проектируйте его. Переоцените, нужна ли вам безопасность транзакций или вам нужно фактически повторно использовать транзакцию для нескольких запросов вместо создания нескольких транзакций с парой запросов на одну транзакцию. с небольшим количеством запросов на транзакцию по сравнению с порождением небольшого количества транзакций с большим количеством запросов. Единственное реальное беспокойство - последовательность.

Любое ожидание - замаскированный выход. Тайм-аут транзакций indexedDB, когда нет ожидающих запросов. Доходность приводит к разрыву во времени, поэтому транзакции будут задерживаться.

...