深入Node源码分析 readline
本文最后更新于:2022年4月22日 上午
深入 Node 源码分析 readline
readline 源码调用栈流程图
首先先提供两张完整的流程图
同步流程图
异步回调流程图
准备
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question('请输入:', answer => {
console.log(answer);
rl.close();
})
运行以上代码后,会在 终端中出现以下内容,
请输入:
同步流程
createInterface
function createInterface(input, output, completer, terminal) {
return new Interface(input, output, completer, terminal);
}
Interface
该方法主要做以下几件事
判断是否通过 构造函数 的方式调用,如果不是则强制通过 构造函数 的方式调用
调用
emitKeypressEvents()
注册 **
keypress
监听,注册 **end
** 监听**
    1. 注册 ** keypress
监听时需要注意,注册完成后判断 是否添加了 ** newListener
** 监听,如果注册了则会广播一个 ** newListener
** 事件**
- 设置成 逐字监听(
this._setRawMode(true)
,这个值默认是false
,逐行监听)
function Interface(input, output, completer, terminal) {
if (!(this instanceof Interface)) {
return new Interface(input, output, completer, terminal);
}
...省略
FunctionPrototypeCall(EventEmitter, this,);
...省略(一系列校验)
function ondata(data) {
self._normalWrite(data);
}
function onend() {
if (typeof self._line_buffer === 'string' &&
self._line_buffer.length > 0) {
self.emit('line', self._line_buffer);
}
self.close();
}
function ontermend() {
if (typeof self.line === 'string' && self.line.length > 0) {
self.emit('line', self.line);
}
self.close();
}
// onkeypress 事件监听
function onkeypress(s, key) {
self._ttyWrite(s, key);
if (key && key.sequence) {
// If the key.sequence is half of a surrogate pair
// (>= 0xd800 and <= 0xdfff), refresh the line so
// the character is displayed appropriately.
const ch = StringPrototypeCodePointAt(key.sequence, 0);
if (ch >= 0xd800 && ch <= 0xdfff)
self._refreshLine();
}
}
function onresize() {
self._refreshLine();
}
this[kLineObjectStream] = undefined;
if (!this.terminal) {
function onSelfCloseWithoutTerminal() {
input.removeListener('data', ondata);
input.removeListener('end', onend);
}
input.on('data', ondata);
input.on('end', onend);
self.once('close', onSelfCloseWithoutTerminal);
this._decoder = new StringDecoder('utf8');
} else {
// 移除监听
function onSelfCloseWithTerminal() {
input.removeListener('keypress', onkeypress);
input.removeListener('end', ontermend);
if (output !== null && output !== undefined) {
output.removeListener('resize', onresize);
}
}
// 调用 emitKeys
emitKeypressEvents(input, this);
// 添加 keypress 和 end 监听
input.on('keypress', onkeypress);
input.on('end', ontermend);
// 设置逐行监听
this._setRawMode(true);
this.terminal = true;
// Cursor position on the line.
this.cursor = 0;
this.history = [];
this.historyIndex = -1;
if (output !== null && output !== undefined)
output.on('resize', onresize);
self.once('close', onSelfCloseWithTerminal);
}
// Current line
this.line = '';
input.resume();
}
emitKeypressEvents
该函数会做以下几件事
调用
emitKeys(stream)
函数,然后调用next()
,使其停留在yield
的位置,接着继续往下执行添加
newListener
事件监听 (因为keypress
事件需要在当前函数执行完才会注册)
function emitKeypressEvents(stream, iface = {}) {
if (stream[KEYPRESS_DECODER]) return;
stream[KEYPRESS_DECODER] = new StringDecoder('utf8');
// 初始化触发 generate,返回一个函数,调用 next 使其停留在 yield 的位置
stream[ESCAPE_DECODER] = emitKeys(stream);
stream[ESCAPE_DECODER].next();
const triggerEscape = () => stream[ESCAPE_DECODER].next('');
const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface;
let timeoutId;
// onData 事件监听
function onData(input) {
if (stream.listenerCount('keypress') > 0) {
const string = stream[KEYPRESS_DECODER].write(input);
if (string) {
clearTimeout(timeoutId);
// This supports characters of length 2.
iface._sawKeyPress = charLengthAt(string, 0) === string.length;
iface.isCompletionEnabled = false;
let length = 0;
for (const character of new SafeStringIterator(string)) {
length += character.length;
if (length === string.length) {
iface.isCompletionEnabled = true;
}
try {
// 将这一次 用户输入的字符传入 generate 函数
stream[ESCAPE_DECODER].next(character);
// Escape letter at the tail position
if (length === string.length && character === kEscape) {
timeoutId = setTimeout(triggerEscape, escapeCodeTimeout);
}
} catch (err) {
// If the generator throws (it could happen in the `keypress`
// event), we need to restart it.
stream[ESCAPE_DECODER] = emitKeys(stream);
stream[ESCAPE_DECODER].next();
throw err;
}
}
}
} else {
// Nobody's watching anyway
stream.removeListener('data', onData);
stream.on('newListener', onNewListener);
}
}
// onNewListener 事件处理函数
function onNewListener(event) {
if (event === 'keypress') {
// 发出 监听 data 事件,然后移除掉 newListener
// 这个事件监听后,命令行会停留在输入的状态
stream.on('data', onData);
stream.removeListener('newListener', onNewListener);
}
}
// keypress 需要在第一次当前函数运行完成后才会注册
if (stream.listenerCount('keypress') > 0) {
stream.on('data', onData);
} else {
// 添加 on newListener 事件
stream.on('newListener', onNewListener);
}
}
onNewListener
这里值得关注一下 onNewListener
,这个事件监听,会在函数结束后注册 keypress
事件的时候触发(使用 on 添加事件的时候最终会判断是否有注册过 newListener
事件,如果有,则会触发),且将 keypress
传入进去。
function onNewListener(event) {
if (event === 'keypress') {
// 发出 监听 data 事件,然后移除掉 newListener
// 这个事件监听后,命令行会停留在输入的状态
stream.on('data', onData);
stream.removeListener('newListener', onNewListener);
}
}
emitKeys
emitKeys
主要会做以下几件事
开启
while(true)
死循环通过
yield
获取到调用next
时传入的字符,第一次调用时会停留在yield
的位置把每次传入的字符当做一个对象来处理
对字符进行一系列判断(下一步骤会具体分析 )
function* emitKeys(stream) {
while (true) {
let ch = yield;
let s = ch;
let escaped = false;
const key = {
sequence: null,
name: undefined,
ctrl: false,
meta: false,
shift: false
};
...以下先省略不看
}
}
到 yield
的时候其实第一次的同步流程就已经结束了,命令行会等待用户输入。
异步流程
异步流程会从用户输入字符开始,当用户在命令行输入字符时
onData
输入流会广播
data
事件(stream.emit('data', chunk)
)可以在这里先打上断点进行调试触发
onData
事件,然后调用emitKeys
的next
方法,将用户输入的字符传入进去
try {
stream[ESCAPE_DECODER].next(character); // 如用户输入了1
}
emitKeys
这一次进入到 emitKeys
时 yield
会接收到用户输入的值
对字符进行一系列校验
广播
keypress
事件
while (true) {
let ch = yield;
let s = ch;
let escaped = false;
const key = {
sequence: null,
name: undefined,
ctrl: false,
meta: false,
shift: false
};
// 判断是否 esc
if (ch === kEscape) {
escaped = true;
s += (ch = yield);
if (ch === kEscape) {
s += (ch = yield);
}
}
// 判断时候 f1~ f12 或 insert、 方向键等
if (escaped && (ch === 'O' || ch === '[')) {
...省略
} else if (ch === '\r') { // 判断是否换行
// carriage return
key.name = 'return';
key.meta = escaped;
} else if (ch === '\n') {
// Enter, should have been called linefeed
key.name = 'enter';
key.meta = escaped;
} else if (ch === '\t') { // tab 键
// tab
key.name = 'tab';
key.meta = escaped;
} else if (ch === '\b' || ch === '\x7f') { // 删除键
// backspace or ctrl+h
key.name = 'backspace';
key.meta = escaped;
} else if (ch === kEscape) { // esc键
// escape key
key.name = 'escape';
key.meta = escaped;
} else if (ch === ' ') { // 空格键
key.name = 'space';
key.meta = escaped;
} else if (!escaped && ch <= '\x1a') {
// ctrl+letter
key.name = StringFromCharCode(
StringPrototypeCharCodeAt(ch) + StringPrototypeCharCodeAt('a') - 1
);
key.ctrl = true;
} else if (RegExpPrototypeTest(/^[0-9A-Za-z]$/, ch)) { // 数字和字符串
// Letter, number, shift+letter
key.name = StringPrototypeToLowerCase(ch); // name 转为小写
key.shift = RegExpPrototypeTest(/^[A-Z]$/, ch); // 判断是否输入为大写
key.meta = escaped;
} else if (escaped) {
// Escape sequence timeout
key.name = ch.length ? undefined : 'escape';
key.meta = true;
}
// 取到字符串
key.sequence = s;
// 发出 keypress 事件
if (s.length !== 0 && (key.name !== undefined || escaped)) {
/* Named character or sequence */
stream.emit('keypress', escaped ? undefined : s, key);
} else if (charLengthAt(s, 0) === s.length) {
/* Single unnamed character, e.g. "." */
stream.emit('keypress', s, key);
}
/* Unrecognized or broken escape sequence, don't emit anything */
}
onkeypress
调用 _ttyWrite
进行写入,准备回显
function onkeypress(s, key) {
self._ttyWrite(s, key);
// 一般情况下不会进入判断
if (key && key.sequence) {
const ch = StringPrototypeCodePointAt(key.sequence, 0);
if (ch >= 0xd800 && ch <= 0xdfff)
self._refreshLine();
}
}
_ttyWrite
这个函数其实最主要也是判断用户输入的内容,根据输入的内容做不同的操作
如果输入的是常规的字符,则最后会调用 this._insertString()
做写入的操作
Interface.prototype._ttyWrite = function(s, key) {
const previousKey = this._previousKey;
key = key || {};
this._previousKey = key;
// ctrl + shift
if (key.ctrl && key.shift) {
/* Control and shift pressed */
switch (key.name) {
// TODO(BridgeAR): The transmitted escape sequence is `\b` and that is
// identical to <ctrl>-h. It should have a unique escape sequence.
case 'backspace':
this._deleteLineLeft();
break;
case 'delete':
this._deleteLineRight();
break;
}
} else if (key.ctrl) { //是否按了 ctrl
/* Control key pressed */
switch (key.name) { // ctrl + c 表示结束
case 'c':
if (this.listenerCount('SIGINT') > 0) {
this.emit('SIGINT');
} else {
// This readline instance is finished
this.close();
}
break;
...省略其它case
}
} else if (key.meta) {
/* Meta key pressed */
switch (key.name) {
case 'b': // backward word
this._wordLeft();
break;
case 'f': // forward word
this._wordRight();
break;
case 'd': // delete forward word
case 'delete':
this._deleteWordRight();
break;
case 'backspace': // Delete backwards to a word boundary
this._deleteWordLeft();
break;
}
} else {
/* No modifier keys used */
// \r bookkeeping is only relevant if a \n comes right after.
if (this._sawReturnAt && key.name !== 'enter')
this._sawReturnAt = 0;
// 正常情况下回进到这个 switch
switch (key.name) {
// 判断是否回车键
case 'return': // Carriage return, i.e. \r
this._sawReturnAt = DateNow();
this._line();
break;
case 'enter':
// When key interval > crlfDelay
if (this._sawReturnAt === 0 ||
DateNow() - this._sawReturnAt > this.crlfDelay) {
this._line();
}
this._sawReturnAt = 0;
break;
case 'backspace':
this._deleteLeft();
break;
case 'delete':
this._deleteRight();
break;
case 'left':
// Obtain the code point to the left
this._moveCursor(-charLengthLeft(this.line, this.cursor));
break;
case 'right':
this._moveCursor(+charLengthAt(this.line, this.cursor));
break;
case 'home':
this._moveCursor(-Infinity);
break;
case 'end':
this._moveCursor(+Infinity);
break;
case 'up':
this._historyPrev();
break;
case 'down':
this._historyNext();
break;
case 'tab':
// If tab completion enabled, do that...
if (typeof this.completer === 'function' && this.isCompletionEnabled) {
const lastKeypressWasTab = previousKey && previousKey.name === 'tab';
this._tabComplete(lastKeypressWasTab);
break;
}
// 当以上都不是时,会进入到这里,基本的字母,数字
// falls through
default:
if (typeof s === 'string' && s) {
const lines = StringPrototypeSplit(s, /\r\n|\n|\r/); // 对字符串进行分割
for (let i = 0, len = lines.length; i < len; i++) {
if (i > 0) {
this._line();
}
this._insertString(lines[i]); // 做回显的操作
}
}
}
}
};
_insertString
缓存字符串
调用
this._writeToOutput(c)
写入
Interface.prototype._insertString = function(c) {
if (this.cursor < this.line.length) {
const beg = StringPrototypeSlice(this.line, 0, this.cursor);
const end = StringPrototypeSlice(this.line, this.cursor, this.line.length);
this.line = beg + c + end;
this.cursor += c.length;
this._refreshLine();
} else {
// 缓存字符串
this.line += c;
this.cursor += c.length;
if (this.getCursorPos().cols === 0) {
this._refreshLine();
} else {
this._writeToOutput(c);
}
}
};
_writeToOutput
判断是否有输入流
调用输出流的
write
方法进行写入
Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) {
validateString(stringToWrite, 'stringToWrite');
// 判断是否有输出流
if (this.output !== null && this.output !== undefined) {
// 调用 输出流的 write 方法写入字符串
this.output.write(stringToWrite);
}
};
异步流程执行到这里就将这一次的字符写入成功了
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。