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>
