20/10/2018, 23:18

Làm việc với stdout và stdin của child_progress trong nodejs

1. Chạy các lệnh trong child_progress const {onExit} = require('@rauschma/stringio'); const {spawn} = require('child_process'); async function main() { const filePath = process.argv[2]; console.log('INPUT: ' + filePath); const childProcess = spawn('cat', [filePath], {stdio: ...

1. Chạy các lệnh trong child_progress

const {onExit} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: ' + filePath);

  const childProcess = spawn('cat', [filePath],
    {stdio: [process.stdin, process.stdout, process.stderr]}); // (A)

  await onExit(childProcess); // (B)

  console.log('### DONE');
}
main();

Ở đây chúng ta dùng lệnh spawn để chạy một lệnh, nó cho phép truy cập vào stdin, stdout và stderr trong khi thực thi lệnh cat. Dòng A cho phép chúng ta kết nối stdin của child_progress với tiến trình hiện tại. Dòng B chúng ta đợi tiến trình đến khi hoàn thành.

Hàm exit trong dòng B được khai báo như sau:

 function onExit(childProcess: ChildProcess): Promise<void> {
  return new Promise((resolve, reject) => {
    childProcess.once('exit', (code: number, signal: string) => {
      if (code === 0) {
        resolve(undefined);
      } else {
        reject(new Error('Exit with error code: '+code));
      }
    });
    childProcess.once('error', (err: Error) => {
      reject(err);
    });
  });
}

2. Ghi trong child_progress

Đoạn mã sau sử dụng @ rauschma / stringio để ghi bất đồng bộ(asynchronously) vào stdin của một tiến trình con(child_progress) đang thực thi một lệnh shell:

const {streamWrite, streamEnd, onExit} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const sink = spawn('cat', [],
    {stdio: ['pipe', process.stdout, process.stderr]}); // (A)

  writeToWritable(sink.stdin); // (B)
  await onExit(sink);

  console.log('### DONE');
}
main();

async function writeToWritable(writable) {
  await streamWrite(writable, 'First line
');
  await streamWrite(writable, 'Second line
');
  await streamEnd(writable);
}

Chúng ta sinh ra một tiến trình riêng biệt, được gọi là sink, cho lệnh shell. Hàm writeToWritable ghi vào sink.stdin. Nó thực thi bất đồng đồng bộ và tạm dừng thông qua await, để tránh đòi hỏi quá nhiều bộ nhớ đệm.

Ở đây:

  • Dòng A, chúng ta truy cập stdin thông qua sink.stdin ('pipe') qua params của spawn. stdout và stderr được chuyển tiếp tới process.stdin và process.stderr, như trước đây
  • Dòng B chúng ta không đợi tiến trình ghi vào steam được hoàn thành xong, mà đợi tiến trinh thực thi sink được thực hiện xong.

Hàm streamWrite đươc khơi tạo như sau:

function streamWrite(
  stream: Writable,
  chunk: string|Buffer|Uint8Array,
  encoding='utf8'): Promise<void> {
    return new Promise((resolve, reject) => {
      const errListener = (err: Error) => {
        stream.removeListener('error', errListener);
        reject(err);
      };
      stream.addListener('error', errListener);
      const callback = () => {
        stream.removeListener('error', errListener);
        resolve(undefined);
      };
      stream.write(chunk, encoding, callback);
    });
}

Ghi vào Nodejs thường liên quan đến các callbacks (doc). Hàm streamEnd() cũng được khởi tạo tương tự.

3.Đọc trong child_progress

Đoạn mã dưới đây sử dụng phép lặp bất đồng bộ (dòng C) để đọc nội dung từ stdout của một tiến trình con (child_progress):

const {chunksToLinesAsync, chomp} = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: '+filePath);

  const source = spawn('cat', [filePath],
    {stdio: ['ignore', 'pipe', process.stderr]}); // (A)

  await echoReadable(source.stdout); // (B)

  console.log('### DONE');
}
main();

async function echoReadable(readable) {
  for await (const line of chunksToLinesAsync(readable)) { // (C)
    console.log('LINE: '+chomp(line))
  }
}

Ở đây:
- Ở dòng A, ta bỏ qua stdin, truy cập vào stdout thông qua stream và chuyển tiếp stderr tới process.stderr.
- Dòng B: ta đang đợi cho đến khi echoReadable () hoàn tất. Nếu không có việc chờ đợi này, DONE sẽ được in trước dòng đầu tiên của source.stdout.

4. Piping giữa child processes

Ví dụ sau sẽ sử dung hàm transform():

  • Đọc nội dung từ stdout của nguồn quá trình con (source child process).
  • Ghi nội dung vào stdin của một quá trình con ngầm (sink child process).
const {chunksToLinesAsync, streamWrite, streamEnd, onExit}
  = require('@rauschma/stringio');
const {spawn} = require('child_process');

async function main() {
  const filePath = process.argv[2];
  console.log('INPUT: '+filePath);

  const source = spawn('cat', [filePath],
    {stdio: ['ignore', 'pipe', process.stderr]});
  const sink = spawn('cat', [],
    {stdio: ['pipe', process.stdout, process.stderr]});

  transform(source.stdout, sink.stdin);
  await onExit(sink);

  console.log('### DONE');
}
main();

async function transform(readable, writable) {
  for await (const line of chunksToLinesAsync(readable)) {
    await streamWrite(writable, '@ '+line);
  }
  await streamEnd(writable);
}

References: http://2ality.com/2018/05/child-process-streams.html

0