I've got these 4 project files:
main.js
preload.js
renderer.js
index.html
Node: 17.4.0 Electron: 18.2.0
I'm attempting to open a text file on my filesystem, triggered by a click event from renderer.js - then load the text file's contents into a <div>
tag in index.html.
main.js
const {app, BrowserWindow, ipcMain} = require("electron");
const path = require("path");
const fs = require("fs");
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
mainWindow.loadFile(path.join(__dirname, "index.html"));
// Open the DevTools.
mainWindow.webContents.openDevTools();
};
app.on("ready", () => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
function openFile(){
fs.readFile("logs.txt", "utf8", (err, data) => {
if (err) {
console.error(err);
return "Error Loading Log File";
}
console.log(data);
return data;
});
}
ipcMain.handle("channel-load-file", openFile);
preload.js
const {contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld("electronAPI", {
loadFile: () => ipcRenderer.invoke("channel-load-file")
});
renderer.js
const btn = document.querySelector("#btn");
btn.addEventListener("click", e => {
let data = window.electronAPI.loadFile();
document.getElementById("main-content").innerText = data;
});
I can definitely see the contents of the Log file inside console.log(data);
in the main.js
But, the <div id="main-content"></div>
gets populated with undefined.
I believe I'm missing some crucial step within either: preload.js
or renderer.js
Anyone see where the chain of events is getting lost?
(I'm very open to any improvements to my flow)
Inserting console.log()
's in the code below indicates that the handle
content is executed before openFile
has
a chance to return a result.
main.js
(main process)
function openFile() {
fs.readFile("logs.txt", "utf-8", (err, data) => {
if (err) {
console.error(err);
return "Error Loading Log File";
}
console.log('openFile: ' + data); // Testing
return data;
});
}
ipcMain.handle('channel-load-file', () => {
let result = openFile();
console.log('handle: ' + result); // Testing
return result;
})
The console.log()
results are...
handle: undefined
openFile: File content...
To fix this, let's change fs.readFile
from a callback to a promise, so we can await
for it in the handle
.
As the handle
is dealing with a promise, let's use the syntactic sugar async
and await
for easier implementation.
main.js
(main process)
function openFile() {
return new Promise((resolve, reject) => {
fs.readFile("logs.txt", "utf-8", (error, data) => {
if (error) {
console.log('reject: ' + error); // Testing
reject(error);
} else {
console.log('resolve: ' + data); // Testing
resolve(data)
}
});
});
}
ipcMain.handle('channel-load-file', async (event, message) => {
return await openFile()
.then((data) => {
console.log('handle: ' + data); // Testing
return data;
})
.catch((error) => {
console.log('handle error: ' + error); // Testing
return 'Error Loading Log File';
})
});
Lastly, let's modify the way we retrieve the data
in the index.html
file.
PS: Let's also add .toString()
to the returned data
(just to be sure).
index.html
(render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body>
<div id="main-content"></div>
<input type="button" id="button" value="Load File">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
window.electronAPI.loadFile()
.then((data) => {
console.log(data); // Testing
document.getElementById("main-content").innerText = data.toString();
});
})
</script>
</html>