Как читать большие файлы (Excel) в NodeJS без приостановки пользовательского интерфейса

В настоящее время я использую ExcelJS для чтения больших файлов Excel (более 10 000 строк) в NodeJS/Angular/ Приложение Электрон. Он отлично читает файлы меньшего размера, но для чтения больших файлов требуется от 3,9 до 5 секунд, и в течение этого времени CSS вообще не обновляется.

В настоящее время я использую async/await для загрузки файлов, так как я думал, что это позволит выполнять другие операции во время ожидания загрузки файла, поскольку я думал, что операции ввода-вывода в узле имеют свой собственный поток. Я также читал, что задачи с интенсивным использованием ЦП блокируют все остальные процессы Node.

Это код, который в настоящее время загружает книгу:

async openWorkbook(filename: string) {
    this.loading = true;
    const workbook = new Workbook();
    const promise = workbook.xlsx.readFile(this.path + '/sheets/' + filename)
      .then(() => {
        // use workbook
        workbook.getWorksheet(1).name = workbook.getWorksheet(1).name.slice(0, 31);
        const excelFile: ExcelFile = { workbook: workbook, filename: filename };
        this.wbSource.next(excelFile);
      });

    const read = await promise;
  }

Этот код работает так же, как реализация обратного вызова, они оба блокируют CSS приложения. Как мне читать файл (или выполнять любую задачу с интенсивным использованием ЦП), не блокируя пользовательский интерфейс?


person MusicDev    schedule 16.08.2019    source источник
comment
Разве это не идеальный кандидат в сервисворкеры? или веб-работники? developer.mozilla.org/en-US/docs/Web/ API/Web_Workers_API/   -  person Deckerz    schedule 16.08.2019
comment
Создайте таймер, используя setInterval для запуска через равные промежутки времени, прочитайте N строк за раз, сделайте что-нибудь с ними, затем выйдите из таймера и разрешите ему снова запускаться, когда все строки будут прочитаны, очистите, остановив таймер и закрыв открытую книгу .   -  person SPlatten    schedule 16.08.2019
comment
@SPlatten - ой! это делает это трудным путем.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
@Deckerz Мне показалось, что я где-то читал, что вам следует избегать использования веб-/сервисных рабочих для задач ввода-вывода?   -  person MusicDev    schedule 16.08.2019
comment
@Iwrestledabearonce, что в этом сложного?   -  person SPlatten    schedule 16.08.2019
comment
@SPlatten, это не обязательно сложно, это просто совершенно не нужно.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
@Iwrestledabearonce., тогда предложи альтернативу?   -  person SPlatten    schedule 16.08.2019
comment
@SPlatten - правильный способ сделать это - использовать работника. Это уже было предложено.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
Под рабочими вы подразумеваете потоки? На самом деле это ничем не отличается от использования таймера, за исключением, вероятно, более требовательного к ресурсам.   -  person SPlatten    schedule 16.08.2019
comment
Для простоты вы можете использовать эту функцию Thread, если хотите, я написал это, чтобы решить аналогичную проблему. где я не хотел поддерживать целый файл для решения одной задачи.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
@SPlatten - таймеры (в потоке пользовательского интерфейса) запускаются в том же потоке, что и пользовательский интерфейс. Использование таймера — это абсолютно не то же самое, что использование рабочего процесса. Таймер необходимо остановить и возобновить, пока рабочий этого не делает.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
Честно говоря, таймер, вероятно, более ресурсоемкий, поскольку он должен постоянно запускать и останавливать ввод-вывод. Вместо того, чтобы делать это за один раз.   -  person Deckerz    schedule 16.08.2019
comment
Поток запускается и работает в цикле до тех пор, пока он не будет завершен, если вы не спите какое-то время, он потребляет ресурсы ЦП, таймер аналогичен, когда он работает в том, что он вызывается, делает что-то, а затем переходит в спящий режим до тех пор, пока он не понадобится опять же, действительно никакой разницы.   -  person SPlatten    schedule 16.08.2019
comment
@SPlatten - ваше понимание функций таймера в корне неверно. таймеры никоим образом не формируют и не формируют новый поток. Учитывая область действия, вы можете написать функцию setTimeout, которая ссылается на глобальную переменную. Если бы таймер был отдельным потоком, глобальные переменные были бы недоступны.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
@Iwrestledabearonce., я никогда не говорил, что таймеры — это потоки, но то, как они работают и занимают время, похоже на поток. Я постоянно разрабатываю многопоточные приложения, поэтому хорошо знаю, как они работают.   -  person SPlatten    schedule 16.08.2019
comment
@SPlatten, извини, мой друг, но то, что ты говоришь, просто неверно. я не пытаюсь вас оскорбить, но если вы используете таймеры, вы не создаете многопоточные приложения. javascript выполняет операторы в очереди. использование таймера берет один оператор и перемещает его дальше в очереди, чтобы его можно было выполнить позже, но он по-прежнему выполняет только один оператор за раз. открытие нового потока позволяет компьютеру одновременно выполнять несколько операторов (несколько очередей). остановись на секунду. вместо того, чтобы пытаться выиграть здесь спор, попытайтесь чему-нибудь научиться.   -  person I wrestled a bear once.    schedule 16.08.2019
comment
тем не менее, решение, которое вы упомянули, безусловно, очень умное. способность так думать — вот что делает программистов великими. Вы просто неправильно поняли, как работает язык. это не на что обижаться.   -  person I wrestled a bear once.    schedule 16.08.2019


Ответы (3)


Первое, что я бы сделал в этом случае, это предотвратил бы накладные расходы памяти, вообще не «открывая» большие файлы.

Итак, что-то вроде этого должно работать:

const stream = fs.createReadStream(filePath);
const workbook = new Excel.Workbook();
stream.pipe(workbook.xlsx.createInputStream());

и поскольку эти фрагменты блокируют цикл на незначительное время, вы можете использовать это даже в цикле событий javascript;)

person Estradiaz    schedule 16.08.2019
comment
Я думаю, что потоки, вероятно, подходят, но я продолжаю получать сообщение об ошибке: аргумент типа «Writable» не может быть назначен параметру типа «WritableStream». - person MusicDev; 16.08.2019
comment
Ничего себе, как раздражает - быстрый обходной путь должен быть stream.on('data', (chunk)=›excelstream.write(chunk)) но я проверю исходный код на github - person Estradiaz; 16.08.2019
comment
@MusicDev Просто чтобы подтвердить, прежде чем я наткнусь на стену - это не ошибка машинописного текста? - person Estradiaz; 16.08.2019
comment
Извините за ожидание, это полностью проблема TypeScript. ExcelJS недавно изменил тип возврата getInputStream: github.com/exceljs/exceljs/issues/575 - person MusicDev; 16.08.2019
comment
@MusicDev - извините - мой ответ - чепуха, поскольку exceljs на самом деле использует потоки, но каким-то образом блокируется до тех пор, пока поток не завершится ... /xlsx.js#L55" rel="nofollow noreferrer">github.com/exceljs/exceljs/blob/ - person Estradiaz; 16.08.2019
comment
Одна вещь, которую я заметил, это то, что анимация загрузки работает, но теперь довольно прерывисто. Думаю, я пока остановлюсь на этом. Благодарю вас! - person MusicDev; 16.08.2019

JavaScript — это однопоточный язык. Если выполнение одной из ваших задач занимает много времени, другие задачи, такие как рендеринг, будут заблокированы. Я не могу написать для вас полный код, но Web Worker кажется идеальным решением для того, что вы пытаетесь сделать. (Что люди уже предложили в комментариях)

Вы можете обратиться:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Кроме того, ознакомьтесь с https://github.com/GoogleChromeLabs/comlink, который является оболочкой для API-интерфейсы веб-воркеров. Это облегчит вам перенос существующего кода в worker с меньшими трудностями.

person vatz88    schedule 16.08.2019

Я просто оставлю свое решение здесь, используя мою функцию Thread из комментариев.

async openWorkbook(filename: string) {
  this.loading = true;

  var path = this.path + '/sheets/' + filename;
  const excelFile = await Thread(path, function(path, exit) {

    // You'll need to import excel again since it's a new thread
    import Excel from 'exceljs';

    const workbook = new Excel.Workbook();
    workbook.xlsx.readFile(path).then(() => {
      workbook.getWorksheet(1).name = workbook.getWorksheet(1).name.slice(0, 31);
      const excelFile: ExcelFile = {
        workbook: workbook,
        filename: filename
      };
      exit(excelFile);
    });
  });

  this.wbSource.next(excelFile);

}

person I wrestled a bear once.    schedule 16.08.2019