如何在Electron主进程就绪后执行单例文件中的函数
解决Electron+Vue中globals.ts过早调用ipcRenderer的问题
问题核心是你在globals.ts里用了立即执行的异步函数,模块加载时就会触发IPC调用,但此时Electron主进程的IPC处理器还没完成注册,或者渲染进程与主进程的通信通道还没建立。以下是几种可靠的解决方式:
方案1:将地址获取逻辑封装为异步函数,延迟调用
把address从立即执行的Promise改成可手动调用的异步函数,在Vue组件的生命周期钩子中(确保渲染进程就绪后)再执行:
修改src/assets/globals.ts
export async function getAddress(): Promise<string> { try { const hostname: string = await window.ipcRenderer.invoke("getMachineIPv4", "please work"); const firstThreeNumbers = hostname.split(".")[0]; const portNumber = "3000"; const subnet = "10"; if (firstThreeNumbers === "123") { return `123.123.1111.${subnet}:${portNumber}`; } else if (firstThreeNumbers === "101") { return `123.123.111.${subnet}:${portNumber}`; } else if (hostname === "localhost") { return `123.123.111.${subnet}:${portNumber}`; } else { console.error("ip地址未找到"); return "error"; } } catch (error) { console.error("获取地址失败:", error); return "error"; } }
在Vue组件中调用(以App.vue为例)
<script setup lang="ts"> import { getAddress } from './assets/globals'; import { onMounted, ref } from 'vue'; const address = ref<string>("加载中..."); onMounted(async () => { // 组件挂载后再调用,此时渲染进程与主进程通信已就绪 address.value = await getAddress(); }); </script> <template> <div>{{ address }}</div> </template>
方案2:确保主进程IPC处理器先注册,再创建窗口
调整主进程代码顺序,先完成IPC处理器的注册,再执行窗口创建逻辑,避免窗口加载时主进程还没准备好响应IPC调用:
修改electron/main/main.ts
import { app, BrowserWindow, ipcMain } from 'electron'; import os from 'os'; import path from 'path'; // 先注册IPC处理函数,再创建窗口 ipcMain.handle("getMachineIPv4", (e, arg) => { console.log("获取ipv4"); const interfaces = os.networkInterfaces(); for (const interfaceName in interfaces) { const interfaceInfo = interfaces[interfaceName]; for (const info of interfaceInfo) { if (info.family === "IPv4" && !info.internal) { return info.address; } } } // 未找到有效IP时返回localhost作为兜底 return "localhost"; }); async function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, '../preload/preload.js') } }); await mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL || `file://${path.join(__dirname, '../../dist/index.html')}`); } app.whenReady().then(() => { createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });
方案3:通过主进程就绪信号触发IPC调用
在预加载脚本中添加等待主进程就绪的方法,主进程窗口加载完成后主动发送就绪信号,渲染进程收到信号后再执行IPC调用:
修改electron/preload/preload.ts
import { contextBridge, ipcRenderer } from 'electron'; contextBridge.exposeInMainWorld("ipcRenderer", { send: (channel: string, data: string) => { const validChannels = ["OpenFolder"]; if (validChannels.includes(channel)) { ipcRenderer.send(channel, data); } }, receive: (channel, func) => { const validChannels = ["fromMain"]; if (validChannels.includes(channel)) { ipcRenderer.on(channel, (event, ...args) => func(...args)); } }, invoke: (channel, arg) => { console.log(`${channel} 被调用`); const validChannels = ["asyncFunc", "getMachineIPv4"]; if (validChannels.includes(channel)) { return ipcRenderer.invoke(channel, arg); } return Promise.reject(new Error("无效的IPC通道")); }, // 添加等待主进程就绪的方法 waitForMainReady: () => { return new Promise<void>((resolve) => { ipcRenderer.once('main-ready', () => resolve()); }); } });
修改electron/main/main.ts的createWindow函数
async function createWindow() { const mainWindow = new BrowserWindow({ // 窗口配置... webPreferences: { preload: path.join(__dirname, '../preload/preload.js') } }); await mainWindow.loadURL('你的Vue应用地址'); // 窗口加载完成后,通知渲染进程主进程已就绪 mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.send('main-ready'); }); }
修改src/assets/globals.ts
export async function getAddress(): Promise<string> { // 先等待主进程就绪信号 await window.ipcRenderer.waitForMainReady(); try { const hostname: string = await window.ipcRenderer.invoke("getMachineIPv4", "please work"); // 后续逻辑不变 } catch (error) { console.error("获取地址失败:", error); return "error"; } }
内容的提问来源于stack exchange,提问作者wren
相关产品推荐
相关产品推荐

