node.jsasynchronousasync-awaitnode-promisify

Node: asynchronous version of readline with util.promisify


I'm trying to create an asynchronous version of readline with promisify.

Something to use this way:

import { Cli } from '../services/Cli';

const cli = new Cli();

const main = async () => {
  await cli.question('What is your name ? ');
  await console.log('\nBYE');

  await process.exit(0);
};

This is my attempt:

import { promisify } from 'util';
import readline from 'readline';

export class Cli {
  private cli;

  constructor() {
    this.cli = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
  }

  question(text: string) {
    return promisify(this.cli.question).call(this.cli, text);
  }
}

I was inspired by other wrapper I have for mysql, which is working nicely:

import { promisify } from 'util';
import mysql from 'mysql';
import config from '../../config.test.json';

export class MySQL {
  private mySQL;

  constructor() {

    this.mySQL = mysql.createConnection(config.database);
  }

  query(sql: string, args?: string | number | [] | {}) {
    return promisify(this.mySQL.query).call(this.mySQL, sql, args);
  }
}

// use it
this.mySQL = new MySQL();
const users = await this.mySQL.query("SELECT * FROM user");
return users

But is not returning anything. Any idea?


Solution

  • The issue is caused by the interface of readline.question() function:

    The callback function passed to rl.question() does not follow the typical pattern of accepting an Error object or null as the first argument. The callback is called with the provided answer as the only argument.

    Promisify works only with standard callback interface:

    akes a function following the common error-first callback style, i.e. taking a (err, value) => ... callback as the last argument, and returns a version that returns promises.

    You need to wrap it:

      question (text) {
        return new Promise(resolve => {
          this.cli.question(text, resolve)
        })
      }
    

    and then

        const name = await cli.question('What is your name ? ')
        console.log(name)