Search code examples
javascriptelectron

How to add a callback to ipc renderer send


Googling says you can add a callback to it, but the documentation just says "arg1, arg2, arg3" etc.

They also have sendSync, but I'd prefer not to block while my event is being sent [we're trying to do as much work through the browser as possible, because writing client code in node, seems a little daft].

If the creators have a sendSync, then surely they have a version with callbacks, or better yet promises.

Some examples of things i'd like to be able to do:

//callback
ipcRenderer.send('anaction', '[1, 2, 3]', function() { console.log('done anaction') });
//promise
ipcRenderer.send('anaction', '[1, 2, 3]')
    .then(function() { console.log('done anaction') });

//sync exists, but it blocks. I'm looking for a non-blocking function
ipcRenderer.sendSync('anacount', '[1, 2, 3]')
console.log('done anaction');

Solution

  • 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    
    })();