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)
CodePudding user response:
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>