0

I am creating a project where I need to take a user input - pass it through a function and return the new value to the user - seems simple enough. I am new to async functions and have read everything I possibly can, and can't works out if there's a more fundamental issue I am missing. I will show the basic code, and then what I wish to achieve. I believe the issue, is that I am returning back the status of the function rather than the value, but just can't work it out.

Basic Code:

ipcMain.on('gpt3', (event, args) => {
    async function gpt3(args) {
        generateResponse('james', 'hello world'); // Takes a user's name & input and recieves a response from a python file.
        event.reply('textRecieve', 'hello world'); // Sends 'Hello World' to the user (ipcRenderer 'textRecieve')
    }
    
    gpt3(args);

})

async function generateResponse(name, text) {
    let testshell = new PythonShell('./python/text_echo.py', { mode: 'text', args: [name, text]});
    let content = "";
    try {
      testshell.on('message', function (message) {
        console.log(message); // prints the output from the python file 'Python File: james Text: hello world'
        return message; // attempting to return the 'Message' from the python file
      });
    } catch (error) {
      console.log("You've f*cked it somewhere my friend");
      console.log(error);
    } 
}

Python Script:

import sys

name = sys.argv[1]
text = sys.argv[2]

print(f'Python File: {name} Text: {text}')
sys.stdout.flush()

Returns: (as expected)

> Executing task: npm run start <


> electron-quick-start@1.0.0 start
> electron .

Python File: james Text: hello world

What I'd Like it to do:

ipcMain.on('gpt3', (event, args) => {
    async function gpt3(args) {
        message = generateResponse('james', 'hello world'); // Takes a user's name & input and recieves a response from a python file, retunring the message to the 'message' variable.
        console.log(message);
        event.reply('textRecieve', 'message would send here'); // Sends the 'Message' to the user (ipcRenderer 'textRecieve')
    }

    gpt3(args);

  })


async function generateResponse(name, text) {
  let testshell = new PythonShell('./python/text_echo.py', { mode: 'text', args: [name, text]});
  let content = ""
  try {
    testshell.on('message', function (message) {
      console.log(message); // prints the output from the python file 'Python File: james Text: hello world'
      return message; // attempting to return the 'Message' from the python file
    });
  } catch (error) {
    console.log("You've f*cked it somewhere my friend")
    console.log(error)
  } 
  return content; // content needs to be message instead due to async nature it returns empty string
}

Returns:

> Executing task: npm run start <


> electron-quick-start@1.0.0 start
> electron .

Promise { '' }
Python File: james Text: hello world

TLDR; I would like to take the 'message' generated through 'generateResponse()' and pass it through to my 'event.reply()'. Instead, I am receiving what I believe to be the status of the Promise. Any help would be greatly appreciated. Thanks

2 Answers2

1

You should resolve the promise first.

ipcMain.on('gpt3', (event, args) => {
    async function gpt3(args) {
        const message = await generateResponse('james', 'hello world'); 
        console.log(message);
        event.reply('textRecieve', 'message would send here'); // Sends the 'Message' to the user (ipcRenderer 'textRecieve')
    }

    gpt3(args);

  })


async function generateResponse(name, text) {
  let testshell = new PythonShell('./python/text_echo.py', { mode: 'text', args: [name, text]});
  let content = ""
  try {
    testshell.on('message', function (message) {
      console.log(message); // prints the output from the python file 'Python File: james Text: hello world'
      content = message;
    });
  } catch (error) {
    console.log("You've f*cked it somewhere my friend")
    console.log(error)
  } 
  return content; // content needs to be message instead due to async nature it returns empty string
}
linusw
  • 1,140
  • 3
  • 9
  • Thanks for getting back to me. I've implemented the code you've sent, but still getting an error. It returns nothing when the `return content;` is there, however, when I comment it out I get `undefined`. It is if the `content = message;` is just being ignored - and is just printing nothing due to `let content = ""` , However when I comment out this line I get a `(node:21133) UnhandledPromiseRejectionWarning: ReferenceError: content is not defined`... but when I just declare it instead `let content` I just get `undefined` again. Any Ideas? Thanks, Elliott – elliott hall Jun 20 '22 at 15:30
  • @elliotthall I am not so much familiar with python-shell library but I can guess the problem is event didn't trigger. you define the shell, define the listener with testshell.on() but I think something like testshell.send() is needed to trigger. – linusw Jun 20 '22 at 16:44
  • Thanks for getting back to me. Very stupidly of me I didn't realise you cant pass variables out of async functions... ‍♂️. Going off this StackO post https://stackoverflow.com/a/23667087/10246221 I was able to work out away of nesting my functions to use the callback(); Certainly not what I expected - but it appears I had two issues, the first being the unresolved promise and the second being the passing of the variables. I will answer the post with my corrected code for anyone else lost with these libraries. Thanks again for your help! – elliott hall Jun 20 '22 at 18:04
0

Okay, so there were a few problems here... but the main was node.js 'non-ability' to pass variables around when 'asynchronous'. with node.js being new to me, I can't lie and say I was confused. Hopefully, the following link to a great workaround/method and my working code will be able to help someone:

https://stackoverflow.com/a/23667087/10246221

Code: ipcMain - nested within app.whenReady().

ipcMain.on('gpt3', (event, input) => {
    gpt3Async(event, input, function(result) { 
      event.reply('textRecieve', result);
      console.log('gpt3Async: '+ result);
      })

  })

Code: Generic 'nested' Function - free-floating around 'main.js' or 'index.js'.

function gpt3Async(event, input, callback) {
    console.log('input: ' + input)
    let testshell = new PythonShell('./python/text_echo.py', { mode: 'text', args: ['elliott' ,input]});
    testshell.on('message', function (message) {
      callback(message);
    });
}

Code: Python Script 'text_echo.py' - in my case within a 'python' subdirectory.

import sys

name = sys.argv[1]
text = sys.argv[2]

print(f'Python File: {name} Text: {text}')
#sys.stdout.flush()
sys.stdout.flush()

For anyone working on a project where you need input and output for python scripts, this will help you out. also make sure you turn on the following:

webPreferences: {
      //preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
      enableRemoteModule: true,
      sandbox: false,
    }, 

BUT!, please be aware of the security implications this will have on your code, More info is available here: https://stackoverflow.com/a/57507392 & https://electronjs.org/docs/tutorial/security#3-enable-context-isolation-for-remote-content & much more so do some reading if this is an important project...

Okay, An explainer, or at least something that blew my mind as a beginner... . The way I finally understood it was through the example link: screenshot of example code from other SO user https://stackoverflow.com/a/23667087/10246221

for some reason, it hadn't clicked with me that functions could be nested within functions like this, all in one line. For someone who is used to JS or node.js this may seem fundamental, but seeing as this is a first-time project to me, and maybe others - if still using python code. Hopefully, this may help!

ipcMain.on('gpt3', (event, input) => { gpt3Async(event, input, function(result) { event.reply('textRecieve', result); console.log('gpt3Async: '+ result);})})