如何在nodejs中获得同步readline,或使用异步“模拟”它?

Posted

技术标签:

【中文标题】如何在nodejs中获得同步readline,或使用异步“模拟”它?【英文标题】:How to get synchronous readline, or "simulate" it using async, in nodejs? 【发布时间】: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) 处理前不需要将整个标准输入读入内存

【问题讨论】:

node.js: readSync from stdin?的可能重复 @supersam654 我添加了一些额外的条件来重复数据删除。我的条件是允许使用 getline 类型函数读取(单)行,使用 readline 接口以干净的“同步”方式或使用“async/await”函数来模拟。 【参考方案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 中也不起作用 ***.com/questions/45556535/…@ColinD 我发现这些答案的句法复杂性是不必要的复杂。我知道这是完全可行的,但我正在寻找像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') 回调中调整代码要容易得多。

以上是关于如何在nodejs中获得同步readline,或使用异步“模拟”它?的主要内容,如果未能解决你的问题,请参考以下文章

如何在nodejs中产生按键检测?

如何在 readline 中定义异步函数

如何将 jquery-ui 与 nodejs 一起使用?

NodeJS 中的 Readline 正在绘制不需要的线条

nodejs的readline怎么支持字符串逐行读取

如何在nodejs中结合同步和异步功能