40行代码实现mini-readline🦄

大家好,我是哈默。今天我们来实现一个简单的 readline,希望能帮助大家理解 readline 的核心原理。

readline 的使用方法

readline 是 node 的一个内置模块。

它的使用方法是:

js 复制代码
const readline = require("readline");

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.question("如何鼓励一下哈默:", (answer) => {
  console.log(`你的选择是:${answer}`);

  rl.close();
});

效果:

接下来,我们就来自己用 40 行代码实现一个 mini-readline 吧。

功能

监听我们在命令行中输入的内容,将输入的内容获取到,并打印出来。

定义核心方法 emitKeypressEvents

首先,我们定义一个 read方法,这个方法接收一个回调函数,在回调函数里,我们可以接收到一个 answer 参数,这个 answer 就是我们 在命令行里输入的内容

js 复制代码
function read() {}

read((answer) => {
  console.log("你输入了:" + answer);
});

readline 的核心逻辑里,会定义一个 emitKeypressEvents 方法,所以在我们的代码里,我们也定义一下这个方法,并执行它:

js 复制代码
function read() {
  emitKeypressEvents();
}

function emitKeypressEvents() {}

同时,我们在 read 方法中,将 输入流输出流保存结果的变量,都定义出来,并将输入流作为参数传入 emitKeypressEvents

js 复制代码
function read() {
  // 输入流
  const input = process.stdin;
  // 输出流
  const output = process.stdout;
  // 保存结果的变量
  let line = "";

  emitKeypressEvents(input);
}

function emitKeypressEvents(stream) {}

emitKeypressEvents 里,我们要做 2 件事情:

  1. 监听用户的输入。
  2. 处理用户的输入。

监听用户输入

首先,我们先来监听用户的输入:

js 复制代码
function emitKeypressEvents(stream) {
  function onData(chunk) {
    console.log("输入的内容:", chunk.toString());
  }
  stream.on("data", onData);
}

这里的 stream,是我们传入的输入流,我们通过在输入流上监听 data 事件,便可以让进程 处于等待用户输入的状态

并且,我们可以打印出,我们输入的内容。

处理用户的输入

能够获取到用户输入的内容之后,我们还需要处理用户的输入。

首先,我们需要定义一个方法,能够不断处理用户输入的内容,我们将它取名为 emitKeys

这个函数比较特殊,是一个 generator function:

js 复制代码
function emitKeypressEvents(stream) {
  function onData(chunk) {
    console.log("输入的内容:", chunk.toString());
  }
  stream.on("data", onData);

  const g = emitKeys(stream);
  g.next();
}

function* emitKeys(stream) {
  // 开启一个死循环,不断处理用户输入的内容
  while (true) {
    // 输入的字符
    let character = yield;
    // 广播一个 keypress 事件,参数为输入的字符
    stream.emit("keypress", character);
  }
}

当我们执行 g.next() 的时候,emitKeys 函数会执行起来,并且只会执行到 let character = yield; 这一行,并停止执行。

直到下一次我们再调用 g.next() 的时候,才会 继续执行 let character = yield; 后面的代码。这个后续代码的执行时机,大家在心里记一下,后面会涉及。

广播 keypress 事件

现在我们能够获取到用户的输入内容,并且也定义了不断处理用户输入的函数,那最后一步就是我们要在用户输入回车的时候,将输入的内容传入到我们使用时候的 read 函数的参数中。

那么,首先,我们就可以在 onData,也就是获取到用户输入的地方,从原来的 console.log 变成 g.next(chunk.toString())。

我们给 g.next() 传入的参数(即 chunk.toString())是我们输入的内容,而这个参数会通过 emitKeys 里的 yield 关键字返回,保存到 character 变量中:

js 复制代码
let character = yield;

然后通过输入流广播一个 keypress 事件,参数为输入的内容。

那么此时,我们就需要有一个地方来监听这里广播的 keypress 事件。

监听 keypress 事件

我们可以将这个监听 keypress 事件的函数定义在 read 方法中。

js 复制代码
function read(callback) {
  // keypress 事件的回调
  function onkeypress(character) {}

  ...

  emitKeypressEvents(input);

  input.on("keypress", onkeypress); // 监听 keypress 事件
}

在 onkeypress 中,我们将会处理 keypress 事件,其中就包括:在用户输入 回车键 的时候,调用用户传入的回调函数,并结束整个进程。

这里我们要处理回车的事件,所以我们需要开启输入流的原始模式。

开启后,将终端对字符的所有特殊处理都将被禁用,比如 回车、ctrl + c 结束进程、甚至包括我们输入字符时,字符的回显

这些操作的行为,将完全让我们开发者自己来定义,输入将会逐个字符进行监听。

开启的方式也是比较简单的,我们只需要调用输入流的 setRawMode API 即可。

js 复制代码
function read(callback) {
  // keypress 事件的回调
  function onkeypress(character) {}

  ...

  emitKeypressEvents(input);

  input.on("keypress", onkeypress); // 监听 keypress 事件
  input.setRawMode(true);
}

此时,我们会发现,我们不能输入任何字符了!

所以,我们第一步,就先将输入的内容回显出来。

js 复制代码
function read(callback) {
  function onkeypress(character) {
    // 通过 line 变量,保存输入的内容
    line += character;
    // 向输出流写入我们输入的内容,让我们输入的内容能够回显出来
    output.write(character);
  }

  ...

  emitKeypressEvents(input);

  input.on("keypress", onkeypress); // 监听 keypress 事件
  input.setRawMode(true);
}

此时,我们又能输入字符了,但会发现回车键,ctrl + c 依旧无法使用。

那么,我们就继续对于回车键进行处理,即 character 为 \r 的时候,将用户输入的所有内容传入到用户给的回调函数中:

js 复制代码
function read(callback) {
  function onkeypress(character) {
    // 通过 line 变量,保存输入的内容
    line += character;
    // 向输出流写入我们输入的内容,让我们输入的内容能够回显出来
    output.write(character);

    switch (character) {
      // 当输入回车键的时候,我们调用用户传入的回调函数 callback
      case "\r":
        // 将输入的内容,传入到 callback 中
        callback(line);
        break;
    }
  }

  ...

  emitKeypressEvents(input);

  input.on("keypress", onkeypress);
  input.setRawMode(true);
}

此时,我们会发现,我们接近成功了!

我们可以看到我们的回调函数执行了,并得到了结果:

js 复制代码
// 我们给 read 函数的回调函数执行了,并得到了 answer 的结果
read((answer) => {
  console.log("你输入了:" + answer);
});

最后一个问题!我们的进程没有结束,所以最后我们需要将输入流中断下来,我们可以通过输入流的 pause API:

js 复制代码
function read(callback) {
  function onkeypress(character) {
    // 通过 line 变量,保存输入的内容
    line += character;
    // 向输出流写入我们输入的内容,让我们输入的内容能够回显出来
    output.write(character);

    switch (character) {
      // 当输入回车键的时候,我们调用用户传入的回调函数 callback
      case "\r":
        // 将输入的内容,传入到 callback 中
        callback(line);
        // 将输入流中断下来
        input.pause();
        break;
    }
  }

  ...

  emitKeypressEvents(input);

  input.on("keypress", onkeypress);
  input.setRawMode(true);
}

我们再来试一下:

可以看到,输入流中断后,进程也成功终止了。

至此,我们的 mini-readline 也就实现完了。

总结

我们实现了一个简单的 readline,mini-readline。

原理是比较简单的,就是:监听用户的输入,并处理用户的输入,最终调用用户传入的回调。

但其中的过程,如果初次接触的话,可能需要多看几遍,并自己亲自尝试一下,才能够 get 到其中的原理。

相关推荐
qq_3643717226 分钟前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森1 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
new出一个对象5 小时前
uniapp接入BMapGL百度地图
javascript·百度·uni-app
你挚爱的强哥6 小时前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
y先森6 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy6 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189116 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿8 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡8 小时前
commitlint校验git提交信息
前端
虾球xz9 小时前
游戏引擎学习第20天
前端·学习·游戏引擎