-
Notifications
You must be signed in to change notification settings - Fork 168
Open
Description
What is the issue with the Streams Standard?
If cancel
is called immediately after a new ReadableStream
is instantiated, the logic in start
may not be completed before cancel
executes. For example:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const r = new ReadableStream({
async start() {
await sleep(1000);
console.log('start completed');
},
cancel() {
console.log('cancel completed');
}
});
await r.cancel();
// cancel completed
// start completed
As I understand it, this behavior is by design. However, it may be confusing because close
and abort
on WritableStream
do wait for start
to complete:
const w = new WritableStream({
async start() {
await sleep(1000);
console.log('start completed');
},
close() {
console.log('close completed');
}
});
await w.close();
// start completed
// close completed
const w2 = new WritableStream({
async start() {
await sleep(1000);
console.log('start completed');
},
abort() {
console.log('abort completed');
}
});
await w2.abort();
// start completed
// abort completed
In particular, examples 10.4 and 10.5 on the standard imply that cancel
waits for start
to finish before executing because there is no check that fs.open
has completed before trying to call fileHandle.close
:
const fs = require("fs").promises;
function makeReadableFileStream(filename) {
let fileHandle;
return new ReadableStream({
async start() {
fileHandle = await fs.open(filename, "r");
},
// ...
cancel() {
return fileHandle.close(); // Dangerous: fileHandle may still be undefined
}
});
}
It would be useful if examples 10.4 and 10.5 made an explicit note about this behavior. The example code can also be updated to block cancel
until start
is completed. For example:
const fs = require("fs").promises;
function makeReadableFileStream(filename) {
let fileHandle;
+ const { promise, resolve } = Promise.withResolvers();
return new ReadableStream({
async start() {
fileHandle = await fs.open(filename, "r");
+ resolve();
},
// ...
- cancel() {
+ async cancel() {
+ await promise;
return fileHandle.close();
}
});
}
Metadata
Metadata
Assignees
Labels
No labels