Node.js как синхронно считывать строки из потока.Читаемый

#javascript #node.js #stream #child-process

Вопрос:

Я взаимодействую с дочерним процессом через stdio, и мне нужно ждать строки из ChildProcess.stdout каждый раз, когда я пишу какую-либо команду в ChildProcess.stdin.
Легко обернуть асинхронный метод для записи, как показано ниже:

 async function write(data){
    return new Promise(resolve=>{
        childProcess.stdin.write(data,()=>resolve());
    })
}
 

Однако это оказывается довольно сложным, когда дело доходит до чтения, поскольку данные из stdout должны обрабатываться с помощью прослушивателей. Я пробовал ниже:

 const LineReader = require("readline")
const reader = LineReader.createInterface(childProcess.stdout);
async function read(){
    return new Promise(resolve=>{
        reader.once("line",line=>resolve(line));
    })
}
 

Я знаю, что могу добиться этого с помощью setInterval
, но он всегда возвращает первую строку., И я уже реализовал эту функциональность таким образом. Но это, очевидно, влияет на производительность, поэтому сейчас я пытаюсь оптимизировать его, обернув в асинхронный метод.
Любые предложения и решения будут оценены по достоинству!

Комментарии:

1. Можете ли вы сделать резервную копию нескольких шагов и описать общую проблему, которую вы пытаетесь решить, более подробно? Вы не можете синхронно считывать данные из потока. Они так не работают. Итак, ваши две попытки сделать это просто идут по неверному пути. Итак, вам нужно сделать резервную копию нескольких шагов и обрисовать общую проблему (не предполагая какого-либо решения), а затем мы, надеюсь, сможем указать вам полезный путь (это отличается от того, что вы пытаетесь сейчас).

2. @jfriend00 Так вы имеете в виду, что в принципе невозможно преобразовать чтение строк в асинхронный метод?

3. Это зависит от того, что вы под этим подразумеваете? Объект чтения строк, который вы используете, управляется событиями. Он запускает события всякий раз, когда у него появляется новая строка. Он не останавливается и не ждет тебя. Это НЕ зависит от спроса (в том смысле, что вы просите его о следующей строке, и он удерживает эту следующую строку, пока вы не спросите снова). Таким образом, вы должны использовать этот интерфейс в соответствии с событиями. Как я уже говорил выше, это было бы намного полезнее, если бы вы описали проблему верхнего уровня, чтобы мы могли указать вам правильный путь решения, а не обсуждать детали пути решения, на котором вы находитесь, что мне кажется неправильным.

4. @jfriend00 Ну что ж, сначала спасибо за ваш ответ. Причина, по которой я не упомянул главную проблему, заключается в том, что это, похоже, не имеет значения. Поскольку вы настаивали, я кратко опишу это. Я взаимодействую со скриптом python, который получает команды от stdin и выводит результаты в stdout. И моему сценарию js нужно дождаться результатов после того, как он запишет команду в stdin дочернего процесса python. Надеюсь, я ясно описываю проблему.

5. Дождитесь результата от stdout, чтобы сделать что дальше? И вы хотите дождаться только следующей строки вывода? Или весь вывод? Это звучит как проблема, которую можно было бы решить с помощью небольшой государственной машины, но мне нужно было бы больше понять последовательность операций.

Ответ №1:

Ну, в итоге я получил нечто очень похожее на то, что вы пытались сделать. Это делает некоторые предположения, которые упомянуты в коде, и требует более полной обработки ошибок:

 const cp = require('child_process');
const readline = require('readline');

const child = cp.spawn("node", ["./echo.js"]);
child.on('error', err => {
    console.log(err);
}).on('exit', () => {
    console.log("child exited");
});

const reader = readline.createInterface({ input: child.stdout });

// this will miss line events that occurred before this is called
// so this only really works if you know the output comes one line at a time
function nextLine() {
    return new Promise(resolve => {
        reader.once('line', resolve);
    });
}

// this does not check for stdin that is full and wants us to wait
// for a drain event
function write(str) {
    return new Promise(resolve => {
        let ready = child.stdin.write(str, resolve);
        if (!ready) {
            console.log("stream isn't ready yet");
        }
    });
}

async function sendCmd(cmd) {
    // get line reader event handler installed so there's no race condition
    // on missing the return event
    let p = nextLine();
    // send the command
    await write(cmd);
    return p;
}

// send a sequence of commands and get their results
async function run() {
    let result1 = await sendCmd("hin");
    console.log(`Got '${result1}'`);
    let result2 = await sendCmd("goodbyen");
    console.log(`Got '${result2}'`);
    let result3 = await sendCmd("exitn");
    console.log(`Got '${result3}'`);
}

run().then(() => {
    console.log("done");
}).catch(err => {
    console.log(err);
});
 

И в целях тестирования я запустил его с помощью этого приложения echo:

 process.stdin.on("data", data => {
    let str = data.toString();
    let ready = process.stdout.write("return: "   str, () => {
        if (str.startsWith("exit")) {
            process.exit();
        }
    });
    if (!ready) {
        console.log("echo wasn't ready");
    }
});
 

Комментарии:

1. Да, именно так. Ваше решение было похоже на мое испытание в описании, которое оказалось эффективным. Проблема была не в таком подходе. Поскольку теперь я не могу воспроизвести проблему, я не могу понять, где изначально была ошибка. В любом случае, спасибо за вашу щедрую помощь и эффективное решение!