ElectronでIPCを同期的に行うときは、sendSync
でなく、invoke
を使う。
ということを言いたくて書いていたらIPCの例みたいになったけどまあいいや。
sendSync
sendSync
は実装の見た目は簡潔になるが、レンダラープロセス全体をブロックしてしまう。他にどうしようもない時の最終手段として用いることが勧められている。
// in renderer process
const result = ipcRenderer.sendSync("some-process", args);
doSomething(result);
メインプロセスでは、sendと同じようにipcMain.on
でイベントをリッスンできる。
// in main process
ipcMain.on("some-process", (event, args) => {
const result = doSomethingInMain(args);
event.returnvalue = result;
});
event.returnValue
で返り値を指定できる。
見た目はとても分かりやすい。少しでも重い処理をするならやめた方がいい。
invoke
一方でinvoke
はPromiseを返す。実装はthen
などを使うこともできるが、await
とasync
を使えばさっぱりとした(というかほぼ上と同じ)実装になる。
// async+await version
async function f() {
const result = await ipcRenderer.invoke("some-process-2", args);
// catchも使える
// const result = await ipcRenderer.invoke("some-process-3", args).catch(() => "フェールセーフ");
doSomething(result);
}
// promise version
ipcRenderer.invoke("some-process-2", args).then((result) => {
doSomething(result);
}).catch(() => {
doSomething("フェールセーフ");
});
sendSync
の例も関数内で行うのが大半だと思うので、そこまで変わらない。promise
バージョンは、関数内でなくても書けるのが強みだと思う。関数内でpromise
で書こうとすると処理によっては見た目が複雑になるので、async
とawait
で書いていくといいだろう。
関数内なのに、await
が使えない!というとき(promiseで引っかかるなど)は実装を考え直した方がいい。
メインプロセスでは、ipcMain.handle
を使う。返り値がinvoke
したレンダラープロセス側に返る。
// in main process
ipcMain.handle("some-process", async (event, args) => {
const result = await doHeavyWork(args);
return result;
});
sendSync
よりもこちらを使う方がいいだろう。メインプロセスの方のasync
とawait
は別に使わなくてもいい。
番外編
せっかくなので、他のIPCについても書いておく。
ipcRenderer.send
非同期ならよく使うやつ。メイン側から返信はあってもなくてもいい。しかし、返信は同じ関数にはこない。なぜなら、非同期なので、あっという間にその命令行は通り過ぎるから。返信を受け取るには、あらかじめipcMain
からのイベント(チャンネル)をリッスンしておく必要がある。
// in renderer process
// 購読してないと結果は受け取れない
ipcRenderer.on("kekka-otodoke", (event, result) => {
doSomething(result);
});
ipcRenderer.send("main-de-yoro", arg1);
メインプロセスの方は、event.returnValue
の代わりにevent.reply
を使うという点以外はsendSync
とほぼ変わらない。
ipcMain.on("main-de-yoro", (event, arg1) => {
const result = doHardWork(arg1);
event.reply("kekka-otodoke", result);
});
メインプロセス内で非同期にdoHardWork
が行われる。async
などは必要ない。メインプロセスからの返答が不要なときはこれで十分。
メインプロセス側からレンダラープロセスに向けて自発的に送信
今まではメインプロセスは受け取りに徹してきた。しかし、メインプロセスからIPCしたいこともある。単純にipcMain.send
だろう、ということはない。というかsend
関数はない。送り先が不明なためだろう。メインプロセスは1つだが、レンダラープロセスは複数になる。どのレンダラープロセス宛てかを指定しないといけないようだ。
どうするかというと、大体メインプロセスで保持しているはずのBrowserWindow
のプロパティのwebContents
から送信できる。
// in main process
// 初期化などでBrowserWindowのインスタンスを代入する
let mainWindow;
function doSomething() {
// 何かする。そしてmainWindowのレンダラープロセスにIPC
if(mainWindow !== null) {
mainWindow.webContents.send("main-kara-dayo", args);
}
}
レンダラープロセスでは、今まで述べたsend
の返信の受け取りのようにしておけばいい。
// in renderer process
ipcRenderer.on("main-kara-dayo", (event, result) => {
updateEtc(result);
});
これは、1つのウィンドウだけに送る例だが、単純に全てのウィンドウに送りたいなら、
// in main process
for(const window of BrowserWindow.getAllWindows()) {
window.webContents.send("to-all-windows", "hello windows!");
}
などでいいし、複数の特定ウィンドウならlet
で保持してそれらにsend
すればいい。
使い所を考える
実際に使うときは、何をしたいかをよく考える。
レンダラープロセスからなのか、メインプロセスからなのか。返信が必要なのか、どのタイミングで実行するのか、レスポンスタイムはどのくらいになりそうなのか、他の実装で十分なのか、などたくさん考えることはある。
統一性がある実装は保守もしやすいので、そこは考えておくといい。
参考
以上。
コメント
[…] […]