一个Electron的设计缺陷及应对方案
当你想实现阻止Electron窗口关闭,并弹出询问对话框,提示用户:“文章尚未保存,是否要关闭窗口”这类业务时,那么你99%会碰到这个BUG:
https://github.com/electron/electron/issues/24994
这是我在去年8月份发现的BUG,Electron的作者也已经确认了这个BUG,但遗憾的是现在还没有修复。下面我们就聊聊这个问题,以及应对这个问题的方案。
问题描述
要阻止窗口关闭,必须在窗口的关闭事件中,执行preventDefault操作才行,如下代码所示:
win.on("close", (e) => {
e.preventDefault();
});
然而这个preventDefault的操作,必须同步调用才能生效,所有异步调用preventDefault的操作都没有任何效果,代码如下所示:
win.on("close", async (e) => { console.log("win close"); await new Promise((resolve) => setTimeout(resolve, 1000)); e.preventDefault(); //没有任何作用 });
上述代码中的preventDefault操作就不会起任何作用。这就带来了一个业务问题:我们往往在询问用户并获得用户的允可后才会阻止窗口关闭,比如:“文章尚未保存,您确认关闭窗口吗?”开发者无法在这种异步的询问通知前执行preventDefault操作,就无法正确的阻止窗口关闭。
可能你会想到用dialog模块的showMessageBoxSync方法来完成这个询问操作,如下代码所示:
win.webContents.on('will-prevent-unload', event => { let choice = dialog.showMessageBoxSync({ title:'do you want to close', buttons:['cancel','yes'] }); if(choice === 1) event.preventDefault(); //... })
没错showMessageBoxSync是一个同步方法,但这也会导致整个主进程的JavaScript线程阻塞,你预期在未来发生的所有事件,以及这些事件的回调方法,都不会再执行了(想想看,你的setInterval的回调方法不会定时执行的结果)。
直到用户关闭showMessageBoxSync方法打开的窗口,主进程的JavaScript线程才会恢复,如果用户永远不做出这个选择,那么整个JavaScript线程就会一直等待下去。
应对方案
为了解决这个问题,我们可以通过额外的哨兵变量来处理,代码如下所示:
//import { app, BrowserWindow, dialog } from "electron"; let winCanBeClosedFlag = false; win.on("close", async (e) => { if (!winCanBeClosedFlag) { e.preventDefault(); let choice = await dialog.showMessageBox(win, { title: "do you want to close", message: "你确定要关闭窗口吗?", buttons: ["否", "是"], }); if (choice.response == 1) { winCanBeClosedFlag = true; win.close(); return; } } winCanBeClosedFlag = false; });
默认情况下winCanBeClosedFlag 这个变量的值是false,即不允许用户关闭窗口(此处的preventDefault是同步操作),当我们询问过用户,并且用户做出了确认关闭的选择后,这个变量才会被设置为true。此时立即调用窗口的close方法,这个窗口的close事件被再次触发,因为winCanBeClosedFlag 变量已经被置为true了,所以不会执行preventDefault操作,窗口被正常关闭。
窗口被关闭的同时winCanBeClosedFlag变量又被置为false,以备下一次用户的操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2020-09-30 Electron安装过程深入解析(读完此文解决Electron安装失败导致的无法启动,无法打包的问题)