Open m93a opened 7 months ago
Rewriting the code to use Deno.stdin.readable
results in the same behavior.
async function* readKeys(signal: AbortSignal) {
Deno.stdin.setRaw(true, { cbreak: true });
const decoder = new TextDecoder();
const reader = Deno.stdin.readable.getReader();
while (!signal.aborted) {
const arr = await reader.read();
if (signal.aborted) break;
yield decoder.decode(arr.value);
}
Deno.stdin.setRaw(false);
}
Furthermore, trying to close the reader on abort by adding the following lines...
signal.addEventListener('abort', () => {
reader.cancel();
});
... results in an internal error:
error: Uncaught BadResource: Bad resource ID
Deno.stdin.setRaw(false);
^
at Stdin.setRaw (ext:deno_io/12_io.js:198:5)
Regarding the releaseLock()
method on ReadableStreamDefaultReader
, MDN has the following to say (emphasis mine):
If the reader's lock is released while it still has pending read requests then the promises returned by the reader's
ReadableStreamDefaultReader.read()
method are immediately rejected with a TypeError. Unread chunks remain in the stream's internal queue and can be read later by acquiring a new reader.
Therefore, my code should do exactly what I wanted if I replace the readKeys
function with this code:
async function* readKeys(signal: AbortSignal) {
const decoder = new TextDecoder();
const reader = Deno.stdin.readable.getReader();
signal.addEventListener('abort', () => reader.releaseLock());
try {
Deno.stdin.setRaw(true, { cbreak: true });
while (!signal.aborted) {
const arr = await reader.read();
if (signal.aborted) break;
yield decoder.decode(arr.value);
}
} catch (e) {
if (!(e instanceof TypeError)) throw e;
} finally {
Deno.stdin.setRaw(false);
}
}
However, Deno does not follow the MDN description of the specs, as the entire next line of stdin is consumed and not given to the next reader. In my example this manifests by the fact that after the message “Good job, you pressed keys.” appears, the user has to press Enter two times (the first one is ignored).
The Problem
In my CLI application, I need to read single characters from stdin until an event happens (eg. a timer runs out), and then I need to cancel all the pending reads and change back to normal stdin mode, so that things like
alert
work.This is the code I've used:
If you try to run this, you'll notice that after the “Time's out!” message, the application hangs until you press another button, and only then it will print out “Good job, you pressed keys.” That is because the function
Deno.stdin.read(key)
is still locked and waiting for another key. SinceDeno.stdin.read
doesn't accept an AbortSignal as an argument, there is really no way to cancel the read.One might naïvely think (as I did) that not awaiting the call would fix the problem. It doesn't: the read function is still blocking the stdin stream, and resetting the mode to non-raw before the function finishes makes the problem even worse, as it will only release the stream once the user presses Enter.
A Possible Solution
Adding a second optional parameter of type
AbortSignal
toDeno.stdin.read
fix my issue. However, considering thatDeno.Reader
is deprecated, a solution usingDeno.stdin.readable
would be preferable.