如何关闭通过 TransformStream 传输的 Web 串行端口?
我有一个 Web 串行端口,我想从中读取一些数据。我想通过使用pipeThrough
转换数据,使用TransformStream
进行一些处理(例如,将字节解码为字符串、分离出逻辑消息等)。然而,一旦我这样做了,我就无法再通过调用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
时并未释放,最终的
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 上的锁真的不会传播到上游吗?我是否必须跟踪管道中每个变压器的实例,以便确保将它们全部解锁?
流规范表示管道在管道持续时间内锁定可读和可写流手术。
管道锁定可读和可写流,防止它们在管道操作期间被操纵。这允许实现执行重要的优化,例如直接将数据从底层源传输到底层接收器,同时绕过许多中间队列。
是否有其他方法可以表明“管道操作”已完成?
I have a Web Serial port that I want to read some data from. I'd like to use TransformStream
s to do some processing (e.g. decode bytes to strings, separate out logical messages, etc) by using pipeThrough
to transform the data. However, once I do this, I can no longer release the lock on the port by calling reader.releaseLock()
. What am I doing wrong here?
This code works how I expect (running without dependencies in the browser in a secure context):
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"
}
However, if I use pipeThrough
to send the output through a do-nothing TransformStream
, it all falls apart. The lock isn't released at releaseLock
and the final close
fails to work.
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"
}
What am I doing wrong here? Does releasing the lock on the TransformStream
really not propagate upstream? Do I have to keep track of an instance of every transformer in my pipeline so I can be sure to unlock them all?
The streams spec says that piping locks the readable and writable streams for the duration of the pipe operation.
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.
Is there some other way I have to indicate that the "piping operation" is completed?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如 https://web.dev/serial/#close-port 中所述,
port.close()
如果串行端口的readable
和writable
成员已解锁,则关闭串行端口,这意味着releaseLock()
已被解锁呼吁各自的读者和作者。不过,使用转换流时关闭串行端口有点复杂。调用
reader.cancel()
,然后调用writer.close()
和port.close()
。这会将错误通过转换流传播到底层串行端口。由于错误传播不会立即发生,因此您需要使用之前创建的readableStreamClosed
和writableStreamClosed
Promise 来检测port.ready
和何时发生>port.writable
已解锁。取消读取器会导致流中止;这就是为什么您必须捕获并忽略由此产生的错误。As explained in https://web.dev/serial/#close-port,
port.close()
closes the serial port if itsreadable
andwritable
members are unlocked, meaningreleaseLock()
has been called for their respective reader and writer.Closing a serial port is a bit more complicated when using transform streams though. Call
reader.cancel()
, then callwriter.close()
andport.close()
. This propagates errors through the transform streams to the underlying serial port. Because error propagation doesn't happen immediately, you need to use thereadableStreamClosed
andwritableStreamClosed
promises created earlier to detect whenport.readable
andport.writable
have been unlocked. Cancelling the reader causes the stream to be aborted; this is why you must catch and ignore the resulting error.对于可能关心的人来说,我还发现了这个解决方案,它一直有效,直到不再有效。
await thing.catch(Object)
as no-op 是准确的(同时也很尴尬),但总的来说,经过 1 小时的混乱,我得出的结论是关闭端口并释放所有内容的最简单方法是传递 AbortController.signal。pipelineTo 确实接受第二个
options
参数,该参数又接受一个signal
属性,因此这就是我最终所做的,并最终正确释放并关闭了端口:我希望这个提示将帮助那些像我今天早上一样疯狂的人。
To whom it might concern, I've also found this solution which worked until the point it didn't anymore. The
await thing.catch(Object)
as no-op was spot-on (and awkward at the same time) but in general, after 1 hour messing around I've ust came to the conclusion that the easiest way to close a port and free everything is by passing an AbortController.signal.The pipeTo indeed accepts a second
options
parameter which in turns accepts asignal
property so that this is what I ended up doing and finally freed properly and closed the port:I hope this hint will help those that went as nuts as I did this morning.