webWorker+indexedDB本地缓存

941 阅读2分钟

推荐用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