0

I am reading some .dbf files selected by user and writing that data to an excel file. I am using electron for this purpose. My code is working fine, but it blocks the main thread for a while. Here is my code:

ipcMain.on('create-one-file', (event, reportName, additionalColumns = [], startingRowNo = 5) => {
        // show dialog to choose .dbf files that user wants to write in GST report
        dialog.showOpenDialog({
            filters: [
                { name: 'CSV Files', extensions: ['csv'] }
            ],
            properties: ['openFile', 'multiSelections']
        }, (fileNames) => {
            if (fileNames) {
                event.sender.send('create-one-file-loading-message', 'Reading from template');
                // read the template file
                XLSX.fromFileAsync(path.join(__dirname, `../sample-files/${reportName}.xlsx`)).then((workbook) => {
                    let rowIndex = 0;
                    let count = 0;
                    // loop through all the files
                    async.whilst(
                        () => count < fileNames.length,
                        callback => { // this function will be called on every iteration
                            // start parsing dbf file
                            const parser = new Parser(fileNames[count], { encoding: 'utf-8' });
                            let worksheet = null;
                            let noOfColumns = 0;
                            parser.on('start', () => {
                                event.sender.send('create-one-file-loading-message', `writing ${path.parse(fileNames[count]).name.toLowerCase()} report`);
                                // reset row no. as new sheet is being written
                                rowIndex = 0;
                                // select the sheet to work on
                                worksheet = workbook.sheet(path.parse(fileNames[count]).name.toLowerCase());
                                if (worksheet) {
                                    // get total columns in the worksheet
                                    noOfColumns = (worksheet.row(3) && worksheet.row(3)._node
                                    && worksheet.row(3)._node.children && worksheet.row(3)._node.children.length
                                    && worksheet.row(3)._node.children.length - 1) || 0;
                                }
                            });
                            parser.on('record', (record) => {
                                if (worksheet) {
                                    let cells = [...additionalColumns, ...Object.values(record)];
                                    cells.shift();
                                    cells.shift();
                                    let isNull = true;
                                    cells.forEach((cell, columnIndex) => {
                                        if ((columnIndex + 1) < noOfColumns && cell) {
                                            isNull = false;
                                        }
                                    });
                                    if (!isNull) {
                                        rowIndex = rowIndex + 1;
                                        cells.forEach((cell, columnIndex) => {
                                            if ((columnIndex + 1) < noOfColumns) {
                                                if (!cell || cell === "NaN") cell = "";
                                                worksheet.row(rowIndex + startingRowNo - 1).cell(columnIndex + 1).value(cell);
                                            }
                                        });
                                    }
                                }
                            });
                            parser.on('end', () => {
                                count++;
                                callback(null);
                            });
                            parser.parse(); 
                        },
                        err => {
                            if (err) {
                                event.sender.send('create-one-file-error', err.message, 'Error reading reports');
                            } else {
                                event.sender.send('create-one-file-loading-message', 'Finishing it up');
                                workbook.toFileAsync(`D:/${reportName}.xlsx`).then(() => {
                                    event.sender.send('create-one-file-success', `Successfully created file at D:/${reportName}.xlsx. Click this message to see the file`);
                                }).catch((error) => {
                                    event.sender.send('create-one-file-error', error.message, `Error writing file. Please make sure that D:/${reportName}.xlsx is closed`);
                                });
                            }
                        }
                    );
                }).catch((err) => {
                    event.sender.send('create-one-file-error', err.message, 'Error reading template');
                });
            } else {
                event.sender.send('create-one-file-cancelled');
            }
        });
    });

As you can see in the code that after selecting the files I am calling fromFileAsync function which returns a promise. And as far as I know, promises does not block the caller. Then why on my side, the code is blocking my UI, till a new file has been created?

Vishal
  • 6,238
  • 10
  • 82
  • 158

1 Answers1

0

Promises are not providing parallel JS execution, they just provide asynchronous execution. What's more, the promise resolution events are commonly added to a micro-task queue, which is consumed as part of the ongoing task.

GUI-related events on the other hand are put in the task queue, which only gets served once the micro-task queue is empty.

So if promises resolve immediately, the resolution event will be processed by triggering the involved then callbacks before any GUI event gets a chance of being handled. This is what can give a blocking experience.

Here is a piece of code that demonstrates this blocking behaviour: it creates a short timeout of 0ms, but first triggers a long lasting chain of promises which each resolve immediately. They prevent the timer to trigger its callback. Only when the promise chain finishes can the timer get its turn:

setTimeout(() => console.log(Date.now(), 'timer expired'), 0);

function loop(i) {
    if (i <= 0) return; // end the chain
    Promise.resolve().then(_ => loop(i-1));
}

console.log(Date.now(), 'start');

loop(1000000);

console.log(Date.now(), 'Async promise chain has started...');

Please realise that the visualisation of the console.log output itself is also delayed (for the same reasons), but you can see from the timestamps (in milliseconds) that the promise chain is responsible for the long delay for the timer to get its turn.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • What would be the recommended way to run long async operations without blocking the event loop? – Evans M. Nov 02 '20 at 21:05
  • Web Workers. See [this answer](https://stackoverflow.com/questions/5408406/web-workers-without-a-separate-javascript-file/48470558#48470558) – trincot Nov 02 '20 at 21:39