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();
}
});
}
What is the issue with the Streams Standard?
If
cancelis called immediately after a newReadableStreamis instantiated, the logic instartmay not be completed beforecancelexecutes. For example:As I understand it, this behavior is by design. However, it may be confusing because
closeandabortonWritableStreamdo wait forstartto complete:In particular, examples 10.4 and 10.5 on the standard imply that
cancelwaits forstartto finish before executing because there is no check thatfs.openhas completed before trying to callfileHandle.close: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
canceluntilstartis 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(); } }); }