Search code examples
node.jsasynchronousasync-awaitterminalkeyboard

Node.js - Terminal stop listening to keypress events once started


What I am trying to accomplish is to have an asynchronous function (works with async/await syntax) that captures a single keypress, and then the control flow of the program resumes normally after that point. I have accomplished this using readline and process.stdin, however how do I stop the application from listening to keypress once started?

import readline from 'readline';
import os from 'os'
const eol = os.EOL;

readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true); 

function awaitKeypress() {
    const promise = new Promise<string>( (resolve, reject) => {
        // need to stop listening here after a keypress is captured
        process.stdin.on('keypress', (str, key) => {     
            console.log('Keypress<<', process.stdin)
            resolve('--key-pressed--')
        })
    } )
    return promise
}

async function main() {
    console.log('Press a key');
    const response = await awaitKeypress()
    console.log("Flow resumed, response is:", response)
}

main()

I get the expected output

Press a key
Keypress<<
Flow resumed, response is: --key-pressed--

However each keypress subsequent results in the callback specified in stdin.on being called.

Press a key
Keypress<<
Flow resumed, response is: --key-pressed--
Keypress<<
Keypress<<
Keypress<<

I want to stop listening for keypresses and resume normal application behavior after I have captured a single keypress.


Solution

  • Use removeListener.

    import readline from 'readline';
    import os from 'os'
    const eol = os.EOL;
    
    readline.emitKeypressEvents(process.stdin);
    process.stdin.setRawMode(true);
    
    /**
     * Wait for a keypress
     * @returns Promise
     */
    export async function keypress( key?:string ) {
        
        process.stdin.setRawMode(true); 
    
        const promise = new Promise<KeypressEvent>( (resolve, reject) => {
    
            const listener = (str, keypressEvent: KeypressEvent) => {
    
                if ( keypressEvent.ctrl === true && keypressEvent.name === 'c' ) {
                    process.stdin.removeListener('keypress', listener)
                    process.stdin.setRawMode(false); 
                    process.exit()
                }
                if ( key === undefined || key === keypressEvent.name ) {
    
                    /* here */
                    process.stdin.removeListener('keypress', listener)
                    process.stdin.setRawMode(false); 
                    resolve(keypressEvent)
                }
            }
            process.stdin.on('keypress', listener )
        } )
        return promise
    }