async function_Electron IPC 通信如何使用 async/await 调用?
前言碎語
此想法是在使用 electron 進程間通信(IPC)過程中,無法忍受其 API 的使用不友好性而產生。
為了提高代碼可讀性、可維護性,而不得已造輪子了。
生命在于折騰,其樂無窮。Electron 中 IPC 的通信方式
在 Electron 中分為兩個進程:
因為 Electron 出于安全考慮,渲染進程的 API 是有限制的。因為 web 端可能會加載第三方 js 代碼,不可能讓第三方為所欲為的。
假如在一些場景中我可能要獲取某個文件夾下的文件列表(舉例而已),在 Node 中使用 fs 模塊即可完成,但是在 web 端無法使用 fs API。
這時候就需要使用 IPC 進程通信來解決。
就像是 nodejs 中的子進程一樣通過 spawn 方法開啟一個子進程,兩個進程之間只能通過 IPC 協議通信
主進程:
import { IpcMain } from 'electron' import fs from 'fs'// 監聽渲染進程發來的 getDir 請求 IpcMain.on('getDir', (event, data) => {fs.readdir('path', (err, files) => {if (err) {// 響應渲染進程獲取失敗了event.reply('dir-result', 'err')} else {const result = files.map(/* do something */)// 響應渲染進程獲取到的結果event.reply('dir-result', result)}}) })渲染進程:
const { ipcRenderer } = require('electron')// 先監聽主進程發來的消息 ipcRenderer.on('dir-result', (event, data) => {// do something })// 發送給主進程 ipcRenderer.send('getDir', '/Users')代碼很簡單,主進程先監聽渲染進程消息,渲染進程再監聽主進程消息,然后渲染進程發起消息。
很好理解,只不過渲染進程的操作過于繁瑣。
假如此邏輯放入vue 組件中,那么需要在 created 中進行監聽主進程消息,在 destroyed 中進行解綁改事件。
那么怎么去優化此處的體驗呢?造輪子唄~
對 IPC 通信方式進行二次封裝
先看下面這段代碼:
此處為渲染進程部分代碼,以 React 組件舉例。// 渲染進程 import React, { useState, useEffect } from 'react' import request from './request'export default function IpcTest () {const [list, setList] = useState([])useEffect(() => {init()}, [])// 進行初始化操作const init = async () => {// request 取代 ipcRenderer.send 和 ipcRenderer.onconst result = await request('test', { a: 1 })// result => ['1', '2', '3'] from Main ProcesssetList(result)} }// 主進程 import server from './server' // 類似 koa-router 的使用方式 server.use('test', async (ctx, data) => {// data => { a: 1 } from Renderer Processconst result = await doSomething(data)ctx.reply(['1', '2', '3']) })上面代碼以 http 請求的寫法來處理了 IPC 通信。其中 request 與 server 則是進行二次封裝后的輪子。
這樣使用 async/await 的方式來處理,是不是就輕松多了?代碼的可讀性、可維護性也增強了。
而且在封裝的過程中完全可以按照 axios 的 API 來處理,便于代碼遷移(當然這里業務邏輯的通用性另說)。
廢話不多說下面來看一下處理原理。
Electron IPC 異步封裝request 大致實現邏輯:
const { ipcRenderer } = require('electron')const _map = new Map()ipcRenderer.on('from-server', (event, params) => {const cb = _map.get(params.symbol)if (typeof cb === 'function') {_map.delete(params.symbol)cb()} })export default request (type, data) {const _symbol = Date.now() + typereturn new Promise(resolve => {_map.set(_symbol, data => {resolve(data)})ipcRenderer.send('from-type', {_symbol, type, data})}) }server 大致實現邏輯:
import { ipcMain } from 'electron'const _map = new Map()ipcMain.on('from-client', (event, params) => {const reply = function (data) {event.reply('from-server', {_symbol: params._symbol,// data 傳遞給客戶端,最終 resolve 它data})}const ctx = {reply,type: params.type}const cb = _map.get(params.type)if (typeof cb === 'function') {cb(ctx, params.data)} else {// 沒有注冊~} })export default function use (type, callback) {_map.set(type, cb) }一個簡單基礎版的 IPC 封裝就完成了,在此功能上還可以增加一些 timeout、多次調用只獲取最后一次返回的數據類似的功能,這些在原生的 electron IPC API 上是無法實現的(也可能是我文檔看的少沒有發現)。
測試
起初在做單元測試時,走了彎路。
因為 IPC 是兩個進程之間交互的一個過程,當時一直在想如何簡單的啟動一個 electron 容器進行測試。
后來又根據官網介紹想通過 Node 啟動兩個進程來模擬 IPC 交互過程,這其實也是個彎路。
其實需要做的只要保證 use、request 方法能跑通、保證內部邏輯運行正確即可。
最后直接模擬了 ipcRenderer (on 和 send)、ipcMain (on 和 reply),將這 4 個方法的輸入與輸出與 electron 提供的 API 表現一致即可達到目的。
類似于 測試驅動 模擬一個測試環境可以讓代碼正常運行,且表現一致。
總結
以上是生活随笔為你收集整理的async function_Electron IPC 通信如何使用 async/await 调用?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中值滤波去除椒盐噪声_pyt
- 下一篇: python读取图片属性_[Python