How to add a callback to ipc renderer send

Thanks to unseen_damage for this suggestion. https://github.com/electron/electron/blob/master/docs/api/ipc-main.md#sending-messages

// In main process.
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
    if(arg === 'ping')
        event.sender.send('asynchronous-reply', 'pong');
    else
        event.sender.send('asynchronous-reply', 'unrecognized arg');
})

// In renderer process (web page).
const {ipcRenderer} = require('electron')
function callAgent(args) {
    return new Promise(resolve => {
        ipcRenderer.send('asynchronous-message', args)
        ipcRenderer.on('asynchronous-reply', (event, result) => {
            resolve(result);
        })
    });
}

In case anybody is still looking for an answer to this question in 2020, a better way to handle replying from the main thread back to the renderer is not to use send at all, but rather to use ipcMain.handle and ipcRenderer.invoke, which make use of async/await and return Promises:

main.js

import { ipcMain } from 'electron';

ipcMain.handle('an-action', async (event, arg) => {
    // do stuff
    await awaitableProcess();
    return "foo";
}

renderer.js

import { ipcRenderer } from 'electron';

(async () => {
    const result = await ipcRenderer.invoke('an-action', [1, 2, 3]);
    console.log(result); // prints "foo"
})();

ipcMain.handle and ipcRenderer.invoke are compatible with contextBridge, allowing you to expose an API to ask the main thread for data from the renderer thread and do something with it once it comes back.

main.js

import { ipcMain, BrowserWindow } from 'electron';

ipcMain.handle('an-action', async (event, arg) => {
    // do stuff
    await awaitableProcess();
    return "foo";
}

new BrowserWindow({
    ...
    webPreferences: {
        contextIsolation: true,
        preload: "preload.js" // MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY if you're using webpack
    }
    ...
});

preload.js

import { ipcRenderer, contextBridge } from 'electron';

// Adds an object 'api' to the global window object:
contextBridge.exposeInMainWorld('api', {
    doAction: arg => ipcRenderer.invoke('an-action', arg);
});

renderer.js

(async () => {
    const response = await window.api.doAction([1,2,3]);
    console.log(response); // we now have the response from the main thread without exposing
                           // ipcRenderer, leaving the app less vulnerable to attack    
})();