Dexie 更新挂钩中的异步更改

Async changes in Dexie updating hook

我正在尝试找出在存储或更改对象时使用挂钩向对象添加一些字段的最佳方法。

基本思想是 entry 个对象必须包含一堆基于其他条目的一些复杂查询和计算的属性。这些计算出的属性都存储在一个名为derived 的属性 下。每次需要时或从数据库读取时计算 entry.derived 都会非常昂贵。相反,我选择提前填充 derived 属性,挂钩似乎是执行此操作的最佳位置。

这对creating 钩子来说似乎没有问题。但是,如果 entry 中的任何其他 属性 发生更改,我还需要重新生成 derivedupdating 挂钩要求我通过返回更改来提交其他更改,这是有问题的,因为我可以生成更改的唯一方法是通过异步调用。

下面是一些试图演示问题的最小代码。我还没有尝试选项 B,但我怀疑它也不会起作用。

const entryDerivedData = function(entry) {
    // query a bunch of data from entries table then do some calculations
    return db.entries.where('exerciseID').equals(entry.exerciseID).toArray()
        .then(e => {
            // do some calculation and return
            return calculateDerivedData(e);
        });
};

// A: Can't do this because `hook` isn't expecting a generator func
db.entries.hook('updating', function*(mods, primKey, entry, transaction) {
    const derived = yield entryDerivedData(entry);
    return derived;
});

// B: Another possibility, but probably won't work either
db.entries.hook('updating', function(mods, primKey, entry, transaction) {
    transaction.scopeFunc = function() {
        return entryDerivedData(entry)
            .then(derived => {
                // Won't this result in another invocation of the updating hook?
                return db.entries.update(entry.id, {derived});
            });
    };
});

不幸的是,挂钩是同步的,无法在其中进行异步调用。这是要改变的事情,但我不能保证什么时候。希望在接下来的 6 个月左右重写 hook 框架,并允许可以异步的批量 hooks(性能更高)(保持现有 hooks 的向后兼容性)。

到那时,您可以利用挂钩始终在事务中调用这一事实(无论用户是否执行显式事务),并且您可以对当前事务执行其他操作。只需要确保您不会陷入无限循环,因为您的其他更改可能会再次触发您的挂钩。

例如:

db.entries.hook('creating', (primKey, entry, trans) => {
  entryDerivedData(entry).then(derived => {
        db.entries.update(primKey, { derived }).catch (e => {
           // Failed to update. Abort transaction by rethrow error:
           throw new Error ("Could not make sure derived property was set properly");
        });
    });
});

db.entries.hook('updating', (mods, primKey, entry, trans) => {
    if ('derived' in mods) return; // We're the one triggering this change. Ignore.
    // First, apply the mods onto entry:
    var entryClone = Dexie.deepClone(entry);
    Object.keys(mods).forEach(keyPath => {
        if (mods[keyPath] === undefined)
            Dexie.delByKeyPath(entryClone, keyPath);
        else
            Dexie.setByKeyPath(entryClone, keyPath, mods[keyPath]);
    });

    entryDerivedData(entryClone).then(derived => {
        db.entries.update(primKey, { derived }).catch (e => {
           // Failed to update. Abort transaction by rethrow error:
           throw new Error ("Could not make sure derived property was set properly");
        });
    });
});