使用Web Worker优化代码

前段时间有个需求,须要前端导出excel。通常来讲,对于导出大量数据的功能,最好仍是交给后端来作,然然后端老哥并不想作(撕逼失败),只能自力更生。javascript

前端导出excel自己已经有很成熟的库了,好比js-xlsx, js-export-excel,因此实现起来并不难。可是,当导出的数据达到几万条时,就会发现页面产生了明显的卡顿。缘由也很简单: 通常咱们都是基于后端返回的json数据来生成excel,可是后端返回的数据通常都不能直接用来生成数据,咱们还须要进行一些格式化:html

const list = await request('/api/getExcelData');

const format = list.map((item) => {
  // 对返回的json数据进行格式化
  item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
  // ... 省略其余各类操做
});

// 根据json生成excel
const toExcel = new ExportJsonExcel(format).saveExcel();
复制代码

卡顿就发生在对大量数据进行map操做。因为JS是单线程的,因此在进行大量复杂运算时会独占主线程,致使页面的其余事件没法及时响应,形成页面假死的现象。前端

那咱们能不能把复杂的循环操做单独放在一个线程里呢?这时就要请出web workerjava

Web Worker

首先看个简单的例子webpack

<button id="btn1">js</button>
<button id="btn2">worker</button>
<input type="text">
复制代码

index.jsgit

const btn1 = document.getElementById('btn1');

btn1.addEventListener('click', function () {
    let total = 1;

    for (let i = 0; i < 5000000000; i++) {
      total += i;
    }
    console.log(total);
})
复制代码

点击btn1时,js会进行大量计算,你会发现页面卡死了,点击input不会有任何反应github

咱们使用web worker优化代码:web

worker.jsjson

onmessage = function(e) {
  if (e.data === 'total') {
    let total = 1;

    for (let i = 0; i < 5000000000; i++) {
      total += i;
    }
    postMessage(total);
  }
}
复制代码

index.js后端

if (window.Worker) {
  const myWorker = new Worker('worker.js');

  myWorker.onmessage = function (e) {
    console.log('total', e.data);
  };

  const btn1 = document.getElementById('btn1');
  const btn2 = document.getElementById('btn2');

  btn1.addEventListener('click', function () {
    let total = 1;

    for (let i = 0; i < 5000000000; i++) {
      total += i;
    }

    console.log('total', total);
  })

  btn2.addEventListener('click', function () {
    myWorker.postMessage('total');
  });

}
复制代码

点击btn2时,页面并不会卡死,你能够正常的对input进行输入操做

咱们开启了一个单独的worker线程来进行复杂操做,经过postMessageonmessage来进行两个线程间的通讯。

优化导出excel表格

看过前面的例子,咱们能够同理使用web worker进行复杂的map操做

worker.js

onmessage = function(e) {
  const format = e.data.map((item) => {
  // 对返回的json数据进行格式化
  item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
  // ... 省略其余各类操做
});

postMessage(format);
}
复制代码
const myWorker = new Worker('worker.js');

myWorker.onmessage = function (e) {
  // 根据json生成excel
  const toExcel = new ExportJsonExcel(e.data).saveExcel();
};
const list = await request('/api/getExcelData');
myWorker.postMessage(list);
复制代码

固然实际项目,咱们通常都是用webpack打包的,这时就要进行一些特别处理,须要使用worker-loader,能够参考《怎么在 ES6+Webpack 下使用 Web Worker》文章学习。

进一步优化

在上面的代码修改中,咱们只是优化了业务逻辑里面的map操做。由于我使用的js库是js-export-excel,从它的源码里能够看见,对于咱们传进来的数据,它还会再一次forEach循环操做,进行数据的二进制转换。所以,这一步的forEach循环,理论上也能够在web worker里面进行操做。

最简单想到的方法是:

worker.js

onmessage = function(e) {
  const format = e.data.map((item) => {
    // 对返回的json数据进行格式化
    item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
    // ... 省略其余各类操做
  });

  // 直接在worker里面生成excel
  const toExcel = new ExportJsonExcel(format).saveExcel();
}
复制代码

直接在worker.js里面生成excel。然而,saveExcel这个方法须要用到document对象,可是在worker里,咱们不能访问相似window document的全局对象。

所以,只能魔改源码了。。。

真正用到document对象的是源码这一句:

// saveAs和Blob用到了document
saveAs(
  new Blob([s2ab(wbout)], {
    type: "application/octet-stream"
  }),
  _options.fileName + ".xlsx"
);
复制代码

saveExcel方法只需改为:

// 不生成excel,只返回数据
return s2ab(wbout);
复制代码

worker.js

onmessage = function(e) {
  const format = e.data.map((item) => {
    // 对返回的json数据进行格式化
    item.time = moment(item.time).format('YYYY-MM-DD HH:mm');
    // ... 省略其余各类操做
  });

  // saveExcel只返回blob数据
  const blob = new ExportJsonExcel(format).saveExcel();
  postMessage(blob);
}
复制代码

index.js

myWorker.onmessage = function (e) {
  // 在主线程生成excel
  saveAs(
    new Blob([e.data], {
      type: "application/octet-stream"
    }),
   "test.xlsx"
  );
};
复制代码

原理就是:咱们只把数据转换放在worker里,最后生成excel仍然在主线程里完成。

至此,优化完成了!

总结

咱们能够把一些耗性能的操做放在worker线程里(好比大文件上传),这样主线程就能及时响应用户操做而不会形成卡顿现象。须要注意的是,在worker里进行的复杂计算,运行时间并不会变短,有时耗费时间甚至更长,毕竟开启worker也须要消耗必定的性能。

相关文章
相关标签/搜索