Оптимизация кода с помощью Web Workers

JavaScript
Оптимизация кода с помощью Web Workers

Некоторое время назад возникла необходимость экспортировать Excel из внешнего интерфейса. Вообще говоря, для функции экспорта большого объема данных лучше всего оставить это на бэкенде, но бэкенд-брат этого делать не хочет(Слезоточивость), может быть только самостоятельным.

Внешний экспорт самого excel уже имеет очень зрелую библиотеку, такую ​​какjs-xlsx, js-export-excel, так что это не сложно реализовать. Однако, когда экспортированные данные достигают десятков тысяч, будет обнаружено, что страница явно зависла. Причина также очень проста: как правило, мы генерируем Excel на основе данных JSON, возвращаемых серверной частью, но данные, возвращаемые серверной частью, не могут быть напрямую использованы для генерации данных.Нам также необходимо выполнить некоторое форматирование:

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 workerохватывать

Web Worker

Сначала посмотрите на простой пример

<button id="btn1">js</button>
<button id="btn2">worker</button>
<input type="text">

index.js

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 выполнит множество вычислений, вы обнаружите, что страница зависла, и нажатие ввода не будет иметь никакого ответа.

Мы используем веб-воркеры для оптимизации кода:

worker.js

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 страница не застрянет, можно будет нормально вводить ввод

Мы запускаем отдельный рабочий поток для выполнения сложных операций,postMessageа такжеonmessageдля связи между двумя потоками.

Оптимизация экспорта таблиц Excel.

Глядя на предыдущий пример, мы можем аналогичным образом использовать веб-воркеры для выполнения сложных операций с картами.

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, вы можете обратиться к"Как использовать Web Worker в ES6+Webpack"изучение статьи.

дальнейшая оптимизация

В приведенной выше модификации кода мы просто оптимизировали операцию сопоставления в бизнес-логике. Поскольку библиотека js, которую я использую,js-export-excel, из егоисходный кодКак видите, для данных, которые мы передаем, он также снова запустит цикл forEach для выполнения двоичного преобразования данных. Таким образом, цикл forEach на этом шаге теоретически может работать и в веб-воркере.

Самый простой способ, который приходит на ум:

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объект, но в рабочем мы не можем получить доступ к чему-то вроде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 по-прежнему выполняется в основном потоке.

На этом оптимизация завершена!

Суммировать

Мы можем поместить некоторые ресурсоемкие операции в рабочий поток (например, загрузку больших файлов), чтобы основной поток мог своевременно реагировать на действия пользователя, не вызывая задержек. Следует отметить, что время выполнения сложных вычислений в воркере не уменьшится, а иногда и увеличится, ведь запуск воркера тоже потребляет определенное количество производительности.