[Electron] IPC には新しい ipcRenderer.invoke() メソッドを使ったほうが便利 (v7+)


TL;DR;

Electron v7 から、ipcRenderer.invoke()ipcMain.handle() が新たに追加されました。これは、従来まで利用されてきた ipcRenderer.send()ipcRenderer.sendSync() の上位互換のようなものです。今後は積極的にこちらを使ったほうがよさそう。

従来の Renderer <-> Main プロセス間通信 (IPC)

同期: ipcRenderer.sendSync()

文字通り、同期 (Sync) 的にプロセス間通信を行います。
この際に重要なのは、sendSync によって Main プロセスが呼ばれるとその間は Renderer プロセス上の処理は完全にブロックされます。Main プロセスからの応答があるまでは、renderer プロセス側の操作画面はいわゆるフリーズしたような状態になります。描画処理も止まるので、ローディング画面のような CSS アニメーションも容赦なく固まります。

ドキュメントにも下記のように、どうしても使わざるを得ない状況下においてのみ最終手段として使う、そうでない場合は利用を避けることが推奨されています。

⚠️ WARNING: Sending a synchronous message will block the whole renderer process until the reply is received, so use this method only as a last resort. It's much better to use the asynchronous version, invoke().

renderer
// renderer から Main プロセスを呼び出す
const data = ipcRenderer.sendSync('sync-test', 'ping')
console.log(data)
main
ipcMain.on('sync-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  event.returnValue = 'pong'
  return
})
console
> ping
> pong

Main プロセス側での処理が終わってから renderer 側で console.log が走るため、結果は上記のようになります。メリットとして、event.returnValue を通じて呼び出し元にデータを返すことができます。

非同期: ipcRenderer.send()

非同期実行のため、Main プロセスの処理中であっても renderer 側ではフリーズすることなく操作が可能です。

ただし Main プロセス側の処理がいつ終わったのか renderer 側では分からないので、これをトリガーに何らかの処理を行いたい場合、renderer 側で ipcRenderer.on() を定義しておくことで、Main -> rendere 方向の戻りの通信が可能になります。

renderer
// main からの呼び出しを待ち受ける
ipcRenderer.on('async-test-complete', (event, message) => {
    console.log(message)
)}

// renderer から Main プロセスを呼び出す
ipcRenderer.send('async-test', 'ping')
console.log('started')


main
ipcMain.on('async-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  // main から renderer プロセスを呼び出す
  event.sender.send('async-test-complete', 'pong')
  return
})
console
> started
> ping
> pong

新しい Renderer <-> Main プロセス間通信 (IPC)

非同期: ipcRenderer.invoke()

ようやく本題です。Electron version 7+ から利用可能になりました。
こちらも非同期ですが、Main プロセスからは Promise が返ってきます。ですので、await を使って Main プロセスからのデータの受取を下記のようにシンプルに書くことができます。

renderer
// renderer から Main プロセスを呼び出す
const data = await ipcRenderer.invoke('invoke-test', 'ping')
console.log(data)
main
ipcMain.handle('invoke-test', (event, message) => {
  // message には 呼び出し元からのデータ ('ping') が入っている
  console.log(message)
  // renderer プロセスにデータを返す
  return 'pong'
})
console
> ping
> pong

Main 側では、ipcMain.handle() とメソッド名が変わっています。