如何关闭通过 TransformStream 传输的 Web 串行端口?

How can I close a Web Serial port that I've piped through a TransformStream?

我有一个 Web 串行端口,我想从中读取一些数据。我想使用 TransformStreams 通过使用 pipeThrough 来转换数据来进行一些处理(例如将字节解码为字符串,分离出逻辑消息等)。但是,一旦我这样做,我就不能再通过调用 reader.releaseLock() 来释放端口上的锁。我在这里做错了什么?

此代码按我预期的方式工作(运行 在安全上下文中浏览器中没有依赖项):

async serialTestWorking() {
  const port = await navigator.serial.requestPort();

  await port.open({baudRate: 9600});
  console.log("Is locked after open?", port.readable.locked);
  // Prints "Is locked after open? false"

  let reader = port.readable.getReader();
  console.log("Is locked after getReader?", port.readable.locked);
  // Prints "Is locked after getReader? true"

  reader.releaseLock();
  console.log("Is locked after releaseLock?", port.readable.locked);
  // Prints "Is locked after releaseLock? false"

  await port.close();
  console.log("Port closed");
  // Prints "Port closed"
}

但是,如果我使用 pipeThrough 通过无操作 TransformStream 发送输出,它就会崩溃。 releaseLock 时未释放锁,最终 close 失败。

async serialTestBroken() {
  const port = await navigator.serial.requestPort();

  await port.open({baudRate: 9600});
  console.log("Is locked after open?", port.readable.locked);
  // Prints "Is locked after open? false"

  let reader = port.readable.pipeThrough(new TransformStream()).getReader();
  console.log("Is locked after getReader?", port.readable.locked);
  // Prints "Is locked after getReader? true"

  reader.releaseLock();
  console.log("Is locked after releaseLock?", port.readable.locked);
  // Prints "Is locked after releaseLock? true"

  await port.close();
  console.log("Port closed");
  // Doesn't make it to the log line
  //throws "TypeError: Failed to execute 'close' on 'SerialPort': Cannot cancel a locked stream"
}

我在这里做错了什么?释放 TransformStream 上的锁真的不会向上游传播吗?我是否必须跟踪我的管道中每个转换器的实例,以便我可以确保将它们全部解锁?

streams spec表示管道在管道操作期间锁定可读和可写流。

Piping locks the readable and writable streams, preventing them from being manipulated for the duration of the pipe operation. This allows the implementation to perform important optimizations, such as directly shuttling data from the underlying source to the underlying sink while bypassing many of the intermediate queues.

是否有其他方式表明“管道操作”已完成?

https://web.dev/serial/#close-port中所述,如果 readablewritable 成员已解锁,port.close() 将关闭串口,这意味着 releaseLock() 已被调用他们各自的 reader 和 writer.

虽然使用转换流时关闭串行端口有点复杂。调用 reader.cancel(),然后调用 writer.close()port.close()。这通过转换流将错误传播到底层串行端口。由于错误传播不会立即发生,因此您需要使用之前创建的 readableStreamClosedwritableStreamClosed 承诺来检测 port.readableport.writable 何时解锁。取消 reader 会导致流中止;这就是为什么您必须捕获并忽略由此产生的错误。

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    reader.releaseLock();
    break;
  }
  // value is a string.
  console.log(value);
}
// Later...

const textEncoder = new TextEncoderStream();
const writer = textEncoder.writable.getWriter();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

reader.cancel();
await readableStreamClosed.catch(() => { /* Ignore the error */ });

writer.close();
await writableStreamClosed;

await port.close();