readline的执行流程图
流程解析
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;