node.js的readline在终端命令行中的执行流程

1,571 阅读3分钟

readline的执行流程图

readline执行流程剪裁.png

流程解析

readline的解析需要从两个阶段来看,一个是readline的准备阶段,这个阶段实现对用户输入的监听;另外一个是命令行中的输入输出阶段,这个阶段对用户输入的字符做解析,然后回显,根据用户输入的字符触发不同的动作;下面对这两个阶段进行分析。

readline的准备阶段

准备阶段是流程图中从开始到结束的这个过程。可以看到这个阶段关键的方法是emitKeypressEvents这个方法,方法的调用栈中会先调用emitKeys方法,然后给input输入流添加newListener, keypress事件,接着调用setRawMode方法修改input输入流为原生模式,最后调用input输入流的resume方法;

命令行中输入输出

输入输出阶段对应流程中从输入到输出的这个过程。在这个过程中,当用户按下键盘按键在命令行中进行输入时,会触发input输入流上面的data事件,在事件的处理函数中我们拿到用户输入的字符code,将字符code传递给keypress事件的处理函数onkeypress,在这个函数中会根据输入字符的不同执行不同的逻辑,命令行中我们主要来看return回车字符。当输入字符不是回车字符时,需要使用output输出流的write方法将字符回显到命令行中,然后将输入的字符进行缓存,当用户在命令行中输入了回车之后,会调用readline函数传递过来的callback函数,将缓存的字符作为参数传递给callback函数,供用户去做业务逻辑上的处理。当用户需要终止命令行的操作时,需要调用readline的close方法,在这个方法中将使用input输入流的pause方法去停止输入流,然后调用setRawMode方法修改输入流的模式

实现简易readline question方法

index.js

// 导入readline demo
const rl = require('./readline');

// 调用question方法
rl.question('how are you?', (txt) => {
    console.log('my answer is:' + txt);
})

readline.js

// 使用process的输入流stdin, 输出流stdout
const input = process.stdin;
const output = process.stdout;

// question方法
function question(qus, callback) {
    // 输出问题
    output.write(qus);
    // 调用generator, emitKeys方法
    const ge = emitKeys();
    // 执行一步使generator等待下一个字符的到来
    ge.next();
    // 输入流监听newListener事件
    const newListenerFn = () => {
        // 移除newListener事件,防止多次触发newListener事件
        input.removeListener('newListener', newListenerFn);
        // 输入流添加data事件
        input.on('data', (buf) => {
            ge.next(buf.toString());
        });
    };
    input.on('newListener', newListenerFn);
    let line = '';
    // 输入流添加keyPress事件,同时会emit newListener事件
    input.on('keyPress', (code) => {
        // 将输入的字符打印到命令行中
        output.write(code);
        // 缓存输入字符
        line += code;
        // 输入字符为回车时
        if (code === '\r') {
            // 调用回调方法打印line(即answer)
            callback(line);
            // 清空缓存
            line = '';
            // 关闭输入流,方法中将调用input.pause()和[input.setRawMode(false),防止影响后面的输入流]
            close();
        }
    });
    // 设置输入流为原生模式,data事件将对键盘的每个字符进行响应,同时需要自己实现输入字符在命令行中的显示
    input.setRawMode(true);
    // 恢复输入流,防止前面代码暂停输入流
    input.resume();
};

// close方法
function close() {
    input.pause();
    input.setRawMode(false);
};

// generator方法
function* emitKeys() {
    while (true) {
        const code = yield;
        input.emit('keyPress', code);
    }
}

exports.question = question;