【问题标题】:How to get synchronous readline, or "simulate" it using async, in nodejs?如何在nodejs中获得同步readline,或使用异步“模拟”它?
【发布时间】:2017-09-24 02:05:43
【问题描述】:

我想知道是否有一种简单的方法来获得“同步”读取线或至少在 node.js 中获得同步 I/O 的外观

我用这样的东西,但是很尴尬

var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

var i = 0;
var s1 = '';
var s2 = '';

rl.on('line', function(line){
    if(i==0) { s1 = line; }
    else if(i==1) { s2 = line; }
    i++;
})

rl.on('close', function() {
    //do something with lines
})'

如果它像类似的东西一样简单,我更愿意这样做

var s1 = getline(); // or "await getline()?"
var s2 = getline(); // or "await getline()?"

有用的条件:

(a) 不喜欢使用外部模块或 /dev/stdio 文件句柄,我正在向代码提交网站提交代码,但这些在那里不起作用

(b) 可以使用 async/await 或生成器

(c) 应该是基于行的

(d) 处理前不需要将整个标准输入读入内存

【问题讨论】:

  • @supersam654 我添加了一些额外的条件来重复数据删除。我的条件是允许使用 getline 类型函数读取(单)行,使用 readline 接口以干净的“同步”方式或使用“async/await”函数来模拟。

标签: node.js readline


【解决方案1】:

我们可以一起使用promise和process.stdin事件来模拟一个同步输入系统

const { EOL } = require("os");
const getLine = async () =>
    (
        await new Promise((resolve) => {
            process.stdin.on("data", (line) => {
                resolve("" + line);
            });
        })
    ).split(EOL)[0];

const line = await getLine();
console.log(line);

【讨论】:

    【解决方案2】:

    文档中提供了最简单(也是首选)的选项。 https://nodejs.org/api/readline.html#rlquestionquery-options-callback

    const util = require('util');
    const question = util.promisify(rl.question).bind(rl);
    
    async function questionExample() {
      try {
        const answer = await question('What is you favorite food? ');
        console.log(`Oh, so your favorite food is ${answer}`);
      } catch (err) {
        console.error('Question rejected', err);
      }
    }
    questionExample();
    

    【讨论】:

    • 感谢反馈,但我是在读取数据文件,而不是提示用户。
    【解决方案3】:

    以防万一将来有人在这里偶然发现

    Node11.7使用async await添加了对此doc_link的支持

    const readline = require('readline');
    //const fileStream = fs.createReadStream('input.txt');
    
    const rl = readline.createInterface({
      input: process.stdin, //or fileStream 
      output: process.stdout
    });
    
    for await (const line of rl) {
      console.log(line)
    }
    

    记得用async function(){} 包裹它,否则你会得到一个reserved_keyword_error

    const start = async () =>{
        for await (const line of rl) {
            console.log(line)
        }
    }
    start()
    

    要读取单个行,您可以手动使用 async 迭代器

    const it = rl[Symbol.asyncIterator]();
    const line1 = await it.next();
    

    【讨论】:

    • 我可以调用 get a single line 使用这种方法吗?例如,我在循环之前想读一两行,所以我想说像 const header = await rl.next() 但这即使在节点 11.7 中也不起作用
    • 我发现这些答案的句法复杂性是不必要的复杂。我知道这是完全可行的,但我正在寻找像var h1 = await rl.next(); var h2 = await rl.next(); for await (const line of rl) { /* process rest of file */ } 这样简单的东西,但正如我上面提到的,rl 迭代器似乎没有提供这种功能
    • 哦!我明白了,vl 检查 readline 和 for await 的实现
    • @AishwatSingh 当我使用你的代码时,我在等待时遇到语法错误。意外的令牌,预期的(你能验证它是否还在工作吗?
    【解决方案4】:

    你可以把它包装在一个承诺中 -

    const answer = await new Promise(resolve => {
      rl.question("What is your name? ", resolve)
    })
    console.log(answer)
    

    【讨论】:

      【解决方案5】:

      我想这就是你想要的:

      const readline = require('readline');
      
      const rl = readline.createInterface({ input: process.stdin , output: process.stdout });
      
      const getLine = (function () {
          const getLineGen = (async function* () {
              for await (const line of rl) {
                  yield line;
              }
          })();
          return async () => ((await getLineGen.next()).value);
      })();
      
      const main = async () => {
          let a = Number(await getLine());
          let b = Number(await getLine());
          console.log(a+b);
          process.exit(0);
      };
      
      main();
      

      注意:此答案使用实验性功能,需要 Node v11.7

      【讨论】:

      • 这看起来不错,例如,在添加这两个数字之后,您将如何设置一个循环以读取直到输入结束?
      • getLine 如果到达 EOF 将返回 undefined,所以 while (1){ let x = await getLine(); if (x === undefined) 中断; /* 然后使用 x */ } 应该可以工作。
      【解决方案6】:

      readline 模块一样,还有一个名为readline-sync 的模块,它接受同步输入。

      示例:

      const reader = require("readline-sync"); //npm install readline-sync
      let username = reader.question("Username: ");
      const password = reader.question("Password: ",{ hideEchoBack: true });
      if (username == "admin" && password == "foobar") {
          console.log("Welcome!")
      }
      

      【讨论】:

      • 这应该是这个问题的正确答案(这是最佳实践)
      • @johannchopin 不,这不是我想要的方法。我通过标准输入或大型数据集使用管道,而不是问答,这是该模块的作用,即 readline-sync 的设计目的
      • 还有一个叫readline-async,以防有人需要承诺。
      • @johannchopin 这不是最佳实践。 readline-sync 不是标准的。这是一个依赖..
      【解决方案7】:

      试试这个。它仍然不是同步行读取功能的完美复制——例如async 函数稍后仍会发生,因此您的某些调用代码可能会乱序执行,并且您无法从正常的 for 循环中调用它——但它比典型的 .on 更容易阅读或.question 代码。

      // standard 'readline' boilerplate
      const readline = require('readline');
      const readlineInterface = readline.createInterface({
              input: process.stdin,
              output: process.stdout
      });
      
      // new function that promises to ask a question and 
      // resolve to its answer
      function ask(questionText) {
        return new Promise((resolve, reject) => {
          readlineInterface.question(questionText, (input) => resolve(input) );
        });
      }
      
      // launch your program since `await` only works inside `async` functions
      start()
      
      // use promise-based `ask` function to ask several questions
      // in a row and assign each answer to a variable
      async function start() {
        console.log()
        let name = await ask("what is your name? ")
        let quest = await ask("what is your quest? ")
        let color = await ask("what is your favorite color? ")
        console.log("Hello " + name + "! " + 
          "Good luck with " + quest + 
          "and here is a " + color + " flower for you.");
        process.exit() 
      }
      

      更新:https://www.npmjs.com/package/readline-promise 实现了它(源代码:https://github.com/bhoriuchi/readline-promise/blob/master/src/index.js#L192)。它还实现了其他几个功能,但它们看起来也很有用,而且没有过度设计,不像其他一些声称做同样事情的 NPM 包。不幸的是,由于https://github.com/bhoriuchi/readline-promise/issues/5,我无法让它工作,但我喜欢它对中心功能的实现:

      function ask(questionText) {
        return new Promise((resolve, reject) => {
          readlineInterface.question(questionText, resolve);
        });
      }
      

      【讨论】:

      • 我喜欢这个解决方案。使用 Promise 可以利用 async/await 上下文的强大功能,而无需其他库。我宁愿把readline接口作为参数传递给ask函数,这样你就可以使用不同的接口了。
      【解决方案8】:

      使用生成器,您的示例将如下所示:

      var readline = require('readline');
      var rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        terminal: false
      });
      
      var i = 0;
      var s1 = '';
      var s2 = '';
      
      var iter=(function* () {
          s1 = yield;
          i++;
          s2 = yield;
          i++;
          while (true) {
              yield;
              i++;
          }
      })(); iter.next();
      rl.on('line', line=>iter.next(line))
      
      rl.on('close', function() {
          //do something with lines
      })
      

      所以yield 在这里的作用就好像它是一个阻塞getline(),你可以以通常的顺序方式处理行。


      UPD
      async/await 版本可能如下所示:

      var readline = require('readline');
      var rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        terminal: false
      });
      
      var i = 0;
      var s1 = '';
      var s2 = '';
      
      var continuation;
      var getline = (() => {
          var thenable = {
              then: resolve => {
                  continuation = resolve;
              }
          };
          return ()=>thenable;
      })();
      (async function() {
          s1 = await getline();
          i++;
          s2 = await getline();
          i++;
          while (true) {
              await getline();
              i++;
          }
      })();
      rl.on('line', line=>continuation(line))
      
      rl.on('close', function() {
          //do something with lines
      })
      

      在这两个“同步”版本中,i 不用于区分行,仅用于计算行的总数。

      【讨论】:

        【解决方案9】:

        这是一个示例,但它需要在给出结果之前读取整个标准输入,但这并不理想

        var rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
            terminal: false
        });
        
        
        function lineiterator() {
            var currLine = 0;
            var lines = [];
            return new Promise(function(resolve, reject) {
        
                rl.on('line', function (line){
                    lines.push(line)
                })
                rl.on('close', function () {
                    resolve({
                        next: function() {
                            return currLine < lines.length ? lines[currLine++]: null;
                        }
                    });
                })
            })
        }
        

        例子

        lineiterator().then(function(x) {
            console.log(x.next())
            console.log(x.next())
        })
        
        $ echo test$\ntest | node test.js
        test
        test
        

        【讨论】:

          【解决方案10】:

          因为我不知道你需要多少个字符串,所以我把它们都放在一个数组中

          如果您需要更详细的答案或我的答案不准确,请随时发表评论:

          var readline = require('readline');
          var rl = readline.createInterface({
              input: process.stdin,
              output: process.stdout,
              terminal: false
          });
          
          var i = 0;
          var strings = [];
          
          rl.on('line', function(line) {
              // 2 lines below are in case you want to stop the interface after 10 lines
              // if (i == 9)
              //  rl.close()
              strings[i] = line
              i++
          }).on('close', function() {
              console.log(strings)
          })
          // this is in case you want to stop the program when you type ctrl + C
          process.on('SIGINT', function() {
              rl.close()
          })
          

          【讨论】:

          • 这需要将整个标准输入读入内存,我不希望这样,因为我通过标准输入读取大文件并且需要流式传输
          • 它是一个流,所以它进入内存的唯一原因是因为我把它放在变量字符串中,但是如果你用它做其他事情它不会进入内存
          • 我明白了。但我想要类似 perl my $line = &lt;&gt; 的东西。或 C++ 中的 getline。有这么难吗?我觉得我的请求并没有那么古怪,我想象它是通过生成器或 async/await 完成的
          • 真正的问题是你想对所有的行做什么?
          • 目前数据行是通过标准输入管道进入程序的,数据一般有一些标题行,然后是很多数据。标头没有信号,您只是根据问题需要推断它,并且数据和标头包含字符串和数字的混合。因此,控制 getline 函数比在 on('line') 回调中调整代码要容易得多。
          猜你喜欢
          • 1970-01-01
          • 2018-01-15
          • 2016-12-14
          • 2017-06-24
          • 1970-01-01
          • 2021-09-25
          • 2014-03-28
          • 2016-01-22
          • 2019-06-14
          相关资源
          最近更新 更多