Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keypress events may not be triggered on Windows if validate callback contains async actions #402

Open
nicky1038 opened this issue Sep 9, 2023 · 2 comments

Comments

@nicky1038
Copy link

Describe the bug

When showing promtps multiple times, if one of the prompt inputs is validated using async actions, keypresses on other prompts may be missed. Happens only on Windows.

To Reproduce

Steps to reproduce the behavior:

const fetch = require('node-fetch'); // use v2 that still supports CommonJS modules
const prompts = require('prompts');

const promptConfigs = [{
  message: 'Prompt 1',
  validate: async () => {
    await fetch('https://jsonplaceholder.typicode.com/posts');
    return true;
  }
}, {
  message: 'Prompt 2'
}, {
  message: 'Prompt 3'
}];

async function main() {
  for (const c of promptConfigs) {
    await prompts({
      type: 'text',
      ...c
    });
  }
}

main();

Actual behavior

On node 16.20.2: on "Prompt 3" no keypress events are triggered untill Enter key is pressed - then the input can be entered as usual; if you remove fetch call from validate callback then everything will work perfectly.

On node 20.6.1: on "Prompt 3", on the first key press, no keypress event is triggered. The second and all next keypresses will be handled correctly.

Also, I have a project which I cannot post here, there I have the behavior described for node 16.20.2 even when it is run on 20.6.1.

Expected behavior

All key presses should be handled correctly.

System

  • OS: Windows 10 Pro 22H2
  • Terminal: cmd, Poweshell
  • Node version: 16.20.2, 20.6.1

Additional context

It may be Node issue. I encountered similar issues in this repo, still there wasn't anything about validate callback there.

@nicky1038
Copy link
Author

nicky1038 commented Sep 9, 2023

Based on Prompts library code, I have made a minimal example of how this issue can be reproduced using only Node.
input function here is a very rough analog of prompts function configured to recieve text input.
On the third invocation of input function there are the same issues mentioned in my first post - no keypress events are triggered before pressing Enter. Node 20.6.1.

const { stdin, stdout } = require('process');
const readline = require('readline');
const fetch = require('node-fetch');

// makes input stream emit 'keypress' events
function createReadline(is, keypress) {
  const rl = readline.createInterface({
    input: is,
    escapeCodeTimeout: 50
  });

  readline.emitKeypressEvents(is, rl);

  if (is.isTTY) {
    is.setRawMode(true);
  }
		
  is.on('keypress', keypress);
	
  // return a callback that stops emitting 'keypress' events and frees resources
  return () => {
    is.removeListener('keypress', keypress);
		
    if (is.isTTY) {
      is.setRawMode(false);
    }
		
    rl.close();
  };
}

// determine what is needed to do for a key object sent by 'keypress' event
function getActionName(key) {
  function needFinish(key) {
    return (key.ctrl && ['c', 'd'].includes(key.name)) ||
      key.name == 'escape' ||
      ['enter', 'return'].includes(key.name);
  }

  function doNothing(key) {
    return ['backspace', 'delete', 'abort', 'escape', 'tab', 'pagedown', 'pageup',
      'home', 'end', 'up', 'down', 'right', 'left'].includes(key.name) || key.ctrl;
  } 
	
  if (doNothing(key)) {
    return 'noop';
  }
  if (needFinish(key)) {
    return 'finish';
  }
  return 'add-letter';
}

// listen to keypresses until user presses Enter, and then return concatenated letters
function input(is, os, onEndInput = () => undefined) {
  return new Promise((resolve) => {
    os.write('Enter string letter by letter, then press Enter\n');
		
    let result = '';
    const closeReadline = createReadline(is, keypress);
		
    function keypress(_, key) {
      os.write(`Encountered ${key.name}\n`)
			
      const actionName = getActionName(key);
			
      if (actionName == 'finish') {
        endInput();
      } else if (actionName == 'add-letter') {
        result += key.name;
      }
    };
		
    async function endInput() {
      await onEndInput();
      closeReadline();
      resolve(result);
    };
  });		
}

async function asyncAction() {
  await fetch('https://jsonplaceholder.typicode.com/posts');
}

async function main() {
  await input(stdin, stdout, asyncAction);
  await input(stdin, stdout);
  await input(stdin, stdout);
}

main();

@nicky1038
Copy link
Author

Based on the new answer in the issue I opened in node repo, here is a workaround that resolves the issue:

In my example

closeReadline();
resolve(result);

Should be wrapped into setTimeout with zero delay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant