如何结合async forEach/map使用readline.createInterface及问题解析
Node.js循环读取输入的异步问题解析
背景
尝试在Node.js循环中读取多个输入值,已知async/await在for/for-of循环中可串行执行,但在map/forEach这类带回调的循环中无法实现串行。
异步读取输入的基础函数:
const readline = require('readline'); function askQuestion(query) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans); })) }
问题1:为何仅显示最后一个查询信息,输入被三次读取?
对应代码
const takeInput = async () => { let ms = ["shoulder", "waist", "chest"]; let ans = []; const promises = ms.map(async (val) => { const choice = await askQuestion(`what is your ${val}`); ans.push(choice); }); await Promise.all(promises); console.log("Your choice is ", ans); }
原因
map方法会立刻遍历所有元素,同步创建3个异步函数并执行。每个异步函数都会创建独立的readline接口,多个接口同时监听stdin和stdout:
- 多个
rl.question的输出会抢占stdout,最终只有最后一个接口的查询提示能正常显示(前面的被覆盖)。 - 输入内容时,所有监听
stdin的接口都会捕获到这个输入值,因此3个Promise都会拿到相同的输入,导致ans数组出现重复值。
问题2:添加console.log(val)后为何所有查询信息能显示,但输入仍被三次读取?
对应代码
const takeInput = async () => { let ms = ["shoulder", "waist", "chest"]; let ans = []; const promises = ms.map(async (val) => { console.log(val); const choice = await askQuestion(`what is your ${val}`); ans.push(choice); }); await Promise.all(promises); console.log("Your choice is ", ans); }
原因
console.log(val)是同步执行的代码,map遍历数组时会立刻依次打印shoulder、waist、chest,因此三个值都会显示。但readline接口的冲突问题依然存在:
- 三个独立的
rl接口还是会同时监听stdin,输入内容仍会被所有接口捕获,导致重复值。 rl.question的查询提示依然会因为多接口抢占stdout出现混乱,只是同步的console.log不受异步接口冲突的影响。
问题3:全局定义rl后,为何查询提示均为第一个,输入仍被多次读取?
对应代码
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); function askQuestion(query) { console.log("Query is: ", query); return new Promise(resolve => rl.question(query, ans => { rl.close(); resolve(ans); })) } const takeInput = async () => { let ms = ["shoulder", "waist", "chest"]; let ans = []; const promises = ms.map(async (val) => { const choice = await askQuestion(`what is your ${val}`); ans.push(choice); }); await Promise.all(promises); console.log("Your choice is ", ans); }
原因
全局rl是同一个实例,map遍历会立刻连续调用三次askQuestion:
- 每次调用
rl.question时,readline实例会将查询提示写入stdout,但连续调用时,后面的question会覆盖前面的等待状态,最终只有第一个查询提示能正常显示在终端。 - 所有
question的回调都绑定在同一个rl实例上,当输入内容时,所有回调都会被触发,拿到相同的输入值。 - 第一次输入后
rl.close()被调用,关闭了接口,但后续的question调用已经注册了回调,依然会捕获到这次输入。
内容的提问来源于stack exchange,提问作者Exter
相关产品推荐
相关产品推荐

