大概是全网最详细的Electron ipc 讲解(一) 您所在的位置:网站首页 invoke方法返回值 大概是全网最详细的Electron ipc 讲解(一)

大概是全网最详细的Electron ipc 讲解(一)

2023-11-10 22:16| 来源: 网络整理| 查看: 265

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

image.png

前言

你盼世界,我盼望你无 bug 。Hello 大家好!我是霖呆呆。

距离上一次我们相遇,似乎间隔的太久了...应该还是去年了...

对于之前文章的搁置,在此说一声抱歉,今年会慢慢补上来(╹▽╹)。(不过还是那句话,那么靓的你肯定会原谅我的啦)

OKK,谈谈文章吧。由于近期的工作内容主要是以桌面端开发为主,所以会多出一些 electron 相关的文章。这不,这个系列主要就是讲解 electron 中 ipc 模块的内容。文章质量和内容这一块大家可以放心,呆呆还是会以通俗易懂的方式,并结合实际案例去讲好每一块知识点,再对各个部分加以总结。

但由于是系列文章,所以肯定也会有很多相对基础的内容,大佬请略过,如果是 electron 初学者的话,相信你会有所收获(* ̄︶ ̄)。

本系列共有以下几个章节:

主进程与渲染进程的两情相悦 渲染进程与渲染进程的搭桥牵线 定情信物传声筒port

您此次阅读的是第一章节:主进程与渲染进程的两情相悦。

注:以上所有文章都被归档到: github.com/LinDaiDai/n… 中 ,案例都上传至:github.com/LinDaiDai/e… ,欢迎 Start,感谢 Start。

案例版本信息:

electron: v13.6.7 Nodejs: v16.13.2 大纲

谈到 electron 中进程的通信,就不得不谈到官方提供的 ipc 模块。这种方式的通信主要是依赖 electron 提供的 ipcMain 和 ipcRenderer 两个模块。

关于通信分为以下几部分:

渲染进程发送同步/异步消息给主进程 主进程发送同步/异步消息给渲染进程 1. 渲染进程发送同步/异步消息给主进程

渲染进程依赖 ipcRenderer 模块给主进程发送消息,官方提供了三个方法:

ipcRenderer.send(channel, ...args) ipcRenderer.invoke(channel, ...args) ipcRenderer.sendSync(channel, ...args)

channel 表示的就是事件名(消息名称), args 好理解,也就是参数。

这三种方法都可以给主进程发送消息,同时还可以等待主进程的答应,真所谓是 两情相悦,互相来电 ,只是答应的方式不同。

image.png

1.1 ipcRender.send 案例

渲染进程 render.js:

// render.js const { ipcRenderer } = require('electron'); function sendMessageToMain() { ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); }

主进程 main.js:

// main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) })

1、主进程通过 ipcMain.on 来监听渲染进程的消息;

2、主进程接收到消息后,可以回复消息,也可以不回复。如果回复的话,通过 event.reply 发送另一个事件,渲染进程监听这个事件得到回复结果。如果不回复消息的话,渲染进程将接着执行 ipcRenderer.send 之后的代码。

上面提到过了, send 这样的方式,主进程可以给回复,也可以不给回复,但是得通过 event.replay。如果此时你试图用 return 的方式传递返回值的话,结果并不能达到你的预期。

// render.js const { ipcRenderer } = require('electron'); function sendMessageToMain() { const replyMessage = ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); console.log(replayMessage); // undefined } // main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) return '主进程试图传递消息给渲染进程'; })

send 方法的返回值是 undefined ,即使在 ipcMain.on 中试图 return 返回值也是不是行的。

正确的做法是通过 event.reply 发送另一个事件,渲染进程监听这个事件得到回复结果:

渲染进程 render.js:

// render.js const { ipcRenderer } = require('electron'); // send 方法发送,并绑定另一个事件接收返回值 function sendMessageToMain() { ipcRenderer.send('render-send-to-main', '我是渲染进程通过 send 发送的消息'); } ipcRenderer.on('main-reply-to-render', (event, message) => { console.log('replyMessage', message); // 'replyMessage 主进程通过 reply 回复给渲染进程的消息' })

主进程 main.js:

// main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-to-main', (event, message) => { console.log(`receive message from render: ${message}`) event.reply('main-reply-to-render', '主进程通过 reply 回复给渲染进程的消息') })

image.png

1.2 ipcRender.invoke 案例

渲染进程 render.js:

// render.js const { ipcRenderer } = require('electron'); async function invokeMessageToMain() { const replyMessage = await ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); }

主进程 main.js:

// main.js const { ipcMain } = require('electron'); ipcMain.handle('render-invoke-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); return result; }) const asyncWork = async () => { return new Promise(resolve => { setTimeout(() => { resolve('延迟 2 秒获取到主进程的返回结果') }, 2000) }) }

1、主进程通过 ipcMain.handle 来处理渲染进程发送的消息;

2、主进程接收到消息后,可以回复消息,也可以不回复。如果回复消息的话,可以通过 return 给渲染进程回复消息;如果不回复消息的话,渲染进程将接着执行 ipcRenderer.invoke 之后的代码。

3、渲染进程异步等待主进程的回应, invoke 的返回值是一个 Promise 。

为了验证第三点,我们可以不使用 await 来接收 invoke 的返回结果,看看会是什么样:

// render.js const { ipcRenderer } = require('electron'); function invokeMessageToMain() { const replyMessage = ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); // Promise }

打印的结果符合我们的预期,是一个 Promise。

image.png

这里有小伙伴可能会有疑问了,会不会是因为 ipc.Main 那边是一个异步执行函数才会造成这种情况呢?好的,我们将 main.js 也来改造一下:

// render.js const { ipcRenderer } = require('electron'); function invokeMessageToMain() { const replyMessage = ipcRenderer.invoke('render-invoke-to-main', '我是渲染进程通过 invoke 发送的消息'); console.log('replyMessage', replyMessage); // Promise } // main.js ipcMain.handle('render-invoke-to-main', (event, message) => { console.log(`receive message from render: ${message}`) const result = '我是主进程同步的返回结果'; return result; })

我们把 ipcMain.handle 的返回值变为了一个普通的字符串,发现 ipcRender.invoke 那边返回的还是一个 Promise。 这也就验证了我们上面的结论。

1.3 ipcRender.sendSync 案例

渲染进程 render.js:

// render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // '主进程回复的消息' }

主进程 main.js:

// main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-sync-to-main', (event, message) => { console.log(`receive message from render: ${message}`) event.returnValue = '主进程回复的消息'; })

1、主进程通过 ipcMain.on 来处理渲染进程发送的消息;

2、主进程通过 event.returnValue 回复渲染进程消息;

3、如果 event.returnValue 不为 undefined 的话,渲染进程会等待 sendSync 的返回值才执行后面的代码;

4、请保证 event.returnValue是有值的,否则会造成非预期的影响。

前面两点都好理解,关于第三点,会有点绕,这边我们来再写两个例子帮助你理解。

image.png

上面的案例,主进程绑定的处理函数是一个同步的,我们将它换为异步的来看看:

// main.js const { ipcMain } = require('electron'); ipcMain.on('render-send-sync-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) const result = await asyncWork(); event.returnValue = result; }) const asyncWork = async () => { return new Promise(resolve => { setTimeout(() => { resolve('延迟 2 秒获取到主进程的返回结果') }, 2000) }) }

这次我们在执行完一个异步函数 asyncWork 之后再给 event.returnValue 赋值。

结果发现渲染进程那边会在 2 秒之后才打印:

"replyMessage 延迟 2 秒获取到主进程的返回结果"

而且对于渲染进程,以下两种写法,结果都是一样的:

// render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // 'replyMessage 延迟 2 秒获取到主进程的返回结果'' } // 或者改用 async 函数 async function sendSyncMessageToMain() { const replyMessage = await ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程发送给主进程的同步消息'); console.log('replyMessage', replyMessage); // 'replyMessage 延迟 2 秒获取到主进程的返回结果' }

也就是说,不论渲染进程在接收 sendSync 结果的时候,是不是用 await 等待,都会等待结果返回后才向下执行。但如果你已经确定你的请求是一个异步的话,建议还是使用 invoke 去发送消息,这里出于两点原因考虑:

1、方法名 sendSync 就很符合语义,发送同步消息;

2、请求执行的明明是异步代码,但是如果你用 const replyMessage = ipcRenderer.sendSync('xxx') 方式来获取响应信息,会很奇怪。

OKK,上面的第四点谈到了,请保证 event.returnValue 是有值的,否则会照成非预期的影响。让我们也来写个例子:

// render.js const { ipcRenderer } = require('electron'); function sendSyncMessageToMain() { const replyMessage = ipcRenderer.sendSync('render-send-sync-to-main', '我是渲染进程通过 syncSend 发送给主进程的消息'); console.log('replyMessage', replyMessage); // replyMessage {error: "reply was never sent"} console.log('next'); // 这里也会执行 } // main.js ipcMain.on('render-send-sync-to-main', async (event, message) => { console.log(`receive message from render: ${message}`) })

在上面的例子中,主进程那边不对 event.returnValue 做处理,在渲染进程这边将会得到一个错误:

{error: "reply was never sent"}

虽然 next 也会打印,但是如果你再想去发送一次 render-send-sync-to-main 你会发现页面已经卡了...

好的,文章阅读到现在,其实也差不多了。后面的主进程给渲染进程发送消息的方式,其实在介绍渲染进程发送消息的时候,算是已经介绍过了,不过还是让我们来看看吧。

image.png

2. 主进程发送同步/异步消息给渲染进程

与 ipcRenderer 模块对应的就是 ipcMain 了。主进程依赖 ipcMain 模块给渲染进程发送消息,主要有以下几种方式:

event.reply:主进程通过 on 监听消息,如果渲染进程用 send 发送消息时,可以在 on 的回调函数里获取到事件对象并通过 event.reply 发送另一个事件; return :主进程通过 handle 监听处理消息,如果渲染进程用 invoke 发送消息时,可以在 on 的回调函数里通过 return 回复消息; event.returnValue :主进程通过 on 监听消息,如果渲染进程用 sendSync 发送消息时,可以在 on 的回调函数里通过设置 event.returnValue 回复消息; window.webContents.send :主进程通过窗口实例的 webContents 给本窗口内的渲染进程发送消息。

前面三种在渲染进程发送消息那一部分已经介绍过了,接下来让我们来看一下 window.webContents 这种方式吧。

2.1 window.webContents.send 案例 :

这种方式依赖于 webContents 对象,它是我们在项目中新建窗口时,产生的窗口对象上的一个属性。

例如我们在一个窗口完成加载时,发送消息:

// main.js const { app, BrowserWindow } = require('electron') function createWindow() { const window = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, contextIsolation: false }, }) window.loadFile('src/index.html') // 窗口在完成加载时,通过 webContents.send 给渲染进程发送消息 window.webContents.on('did-finish-load', () => { window.webContents.send('main-send-to-render', '启动完成了') }) } app.whenReady().then(createWindow)

渲染进程 render.js :

// render.js const { ipcRenderer } = require('electron'); ipcRenderer.on('main-send-to-render', (event, message) => { console.log(`receive message from main: ${message}`) })

通过以上操作,也可以达到主进程主动给渲染进程发送消息的效果(* ̄︶ ̄)。

3. 总结

OKK,介绍完了功能,分析完了案例,让我们来加以总结一下吧。

如果通过 ipcMain 和 ipcRenderer ,渲染进程有三种方式给主进程发送消息,即: ipcRenderer.send(channel, ...args) ipcRenderer.invoke(channel, ...args) ipcRenderer.sendSync(channel, ...args) 通过上面三种方式,主进程都可以有相应的方法给予渲染进程答应,只是答应的方法不同; 同步请求建议用 sendSync,异步请求建议用 invoke; 主进程中还可以获取到某个渲染进程的 window.webContents ,然后通过 window.webContents.send 给这个渲染进程发送消息;

温馨提示:

案例都上传至:github.com/LinDaiDai/e… ,欢迎 Start,感谢 Start。

后语

这篇文章就介绍到这里。可以发现,这篇文章要讲解的知识点其实还是蛮基础的,但呆呆在刚开始学的时候,也没有发现有特别好的教材讲解,所以这不是"自己动手,丰衣足食"整了一篇吗?

哈哈哈,不过放心,这个系列还是会继续下去的,毕竟 ipc 也还有好些东西要讲,让我们一起期待一下下篇文章:渲染进程与渲染进程的搭桥牵线吧。

喜欢「霖呆呆」的小伙还希望可以关注霖呆呆的公众号 LinDaiDai。

我会不定时的更新一些前端方面的知识内容以及自己的原创文章🎉

你的鼓励就是我持续创作的主要动力 😊.



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有