推荐用dexie+webWorker
web Worker实现远程数据库一致性
问: 本地数据库和远程数据库数据一致性怎么保证呢?
答:所以个人其实更推荐将一些改动幅度比较小的api缓存到本地数据库中。
如果api更改的很频繁,并不建议让其缓存到本地数据库中。
问:那有一些场景既不频繁,但偶尔也有更改的场景应该如何处理呢?
答:可以先使用本地数据库,然后在fetch api进行比对,如果比对结果无diff不做任何处理,
如果有diff的话,我们更新本地数据库,同时rerender页面。
问:这样的话虽然页面展示出来了,但是fetch时候依然占用主线程,导致主线程挂起?
答:所以另一个主角webworker迈着正步向我们走来了,可以起一个webworker代替我们进行
fetch和diff,这样既不需要占用主线程,
// 在worker.js中导出一个worker的方法
export function entitiesWorker() {
// 当收到主线程来信的时候需要做的处理
self.onmessage = e => {
// 请求接口
fetch(self.location.origin + '/api/entities', {
method: 'GET',
}).then( r => r.json()).then(
r => {
// 进行比对
// 或者使用深拷贝
if(JSON.stringify(e.data) === JSON.stringify(r.data[0])) {
// 不需要更新
self.postMessage({
isUpdate: false,
});
} else {
// 需要更新
self.postMessage({
isUpdate: true,
data: r.data,
});
}
}
)
}
}
// 主线程
// 由于web Worker只能通过url引入,同时还有同源策略所以需要我们自己处理worker.js文件
// transWorker 是我们转worker的工具
// 将worker转成一个IIFE之后封装成一个blob 然后传入worker。
export const transWorker = (worker) => {
let blob = new Blob([ "(" + worker.toString() + ')()'], {type: "application/javascript"});
return new Worker(URL.createObjectURL(blob));
}
let apiWorker = transWorker(entitiesWorker);
// 经过上面一番简单的折腾我们的worker就建好了
web Worker和indexedDB通信
上面我们已经写好了web Worker 和 indexedDB,接下来结合两者进行本地数据缓存和单开线程进行数据diff及更新。
const {data} = await dbGet(IndexedDBStampEnum.PRODUCT);
// 如果本地数据库中有数据
if(data) {
// 将本地缓存数据给State
this.setState({ data });
// 单开worker线程
let apiWorker = transWorker(entitiesWorker);
// 由于worker无法访问indexedDB,所以需要手动将数据传给worker线程
apiWorker.postMessage(data);
// 等待worker响应
apiWorker.onmessage = e => {
// 如果数据有diff
if(e.data.isUpdate) {
// 更新数据库及rerender页面
dbPut(e.data.data, IndexedDBStampEnum.PRODUCT);
this.setState({ data: e.data.data });
}
//关闭线程
apiWorker.terminate();
}
} else {
//没有数据则执行api拉取,不要忘记在api层更新indexedDB的数据。
await this.getState();
}
Web Workers 资源跨域问题
async () => {
// 得到字符串化的 JS 代码
const codeString = await fetch(originWorkerUrl).then(res => res.text());
// 在这里我没有使用 new Worker(`data:application/javascript, ${codeString}`) 这种方式
// 而是使用了 URL.createObjectURL 以及 new Blob, 会将 JavaScript 字符串转换为如下格式
// blob:http://same-domain/cd2930c0-f4ca-4a9f-b6b1-8242e520dd62
// 因此不再会有跨域问题
const localWorkerUrl = window.URL.createObjectURL(new Blob([codeString], {
type: 'application/javascript'
}));
}
针对此方案,我封装了一个 npm 包方便使用:cross-domain-worker-url