链式Promise转换的Observable与from+concatMap生成的Observable是否等价?
问题解答
错误根因
- 该约束错误和Dexie/IndexedDB的
clear实现无关,clear返回Promise resolve时已经完成了表格清空,不存在提前返回的问题。 - 错误本质是快速连续两次调用
setItemInfos时,两个独立的异步操作链出现了执行顺序竞态,和单次调用时的链式逻辑无关。
第一种实现的问题
单次调用第一种写法的逻辑是完全符合预期的,clear执行完成后才会触发bulkAdd。但连续触发两次调用时会出现如下异常时序:
- 第一次调用触发异步清空任务
clear_1,返回待执行的Observable - 第二次调用触发异步清空任务
clear_2,返回待执行的Observable clear_1执行完成,触发批量插入任务bulkAdd_1clear_2执行完成,触发批量插入任务bulkAdd_2- 若
bulkAdd_2比bulkAdd_1先执行完成,bulkAdd_1插入时就会遇到已存在的主键,抛出约束错误。
两种实现的等价性判断
两种实现完全不等价,你当前第二种写法未复现错误仅为巧合:from(Promise)接收Promise参数时,Promise会立即执行,也就是说你声明bulkAdd$的那一刻,bulkAdd操作就已经触发了,并不会等待clear$执行完成。现在未复现错误只是测试场景下clear执行速度恰好快于bulkAdd,如果遇到大表清空耗时更长的场景,不仅会出现主键冲突,甚至可能出现bulkAdd先完成、clear后执行,最终所有数据被清空的异常。
如果要使用RxJS运算符实现正确的串行逻辑,应该将bulkAdd的Observable创建放在concatMap的回调中,确保clear完成后才触发插入操作:
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> { return from(this.db.selectionItemInfos.clear()).pipe( concatMap(() => from(this.db.selectionItemInfos.bulkAdd(itemInfos))) ); }
彻底解决竞态的方案
如果要完全避免快速双击导致的并发问题,建议在上层调用setItemInfos的逻辑中增加防抖,或使用switchMap/concatMap管控调用顺序,避免同时存在多个未完成的异步读写流。
内容的提问来源于stack exchange,提问作者char m
相关产品推荐
相关产品推荐

