Skip to content

Commit f40780c

Browse files
committed
fixup! fs: add FileHandle.prototype.readableWebStream()
1 parent ab0107f commit f40780c

File tree

4 files changed

+83
-2
lines changed

4 files changed

+83
-2
lines changed

doc/api/fs.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ const file = await open('./some/file/to/read');
325325

326326
for await (const chunk of file.readableWebStream())
327327
console.log(chunk);
328+
329+
await file.close();
328330
```
329331
330332
```cjs
@@ -337,9 +339,15 @@ const {
337339

338340
for await (const chunk of file.readableWebStream())
339341
console.log(chunk);
342+
343+
await file.close();
340344
})();
341345
```
342346
347+
While the `ReadableStream` will read the file to completion, it will not
348+
close the `FileHandle` automatically. User code must still call the
349+
`fileHandle.close()` method.
350+
343351
#### `filehandle.readFile(options)`
344352
<!-- YAML
345353
added: v10.0.0

lib/internal/fs/promises.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ const {
102102
newReadableStreamFromStreamBase,
103103
} = require('internal/webstreams/adapters');
104104

105+
const {
106+
readableStreamCancel,
107+
} = require('internal/webstreams/readablestream');
108+
105109
const getDirectoryEntriesPromise = promisify(getDirents);
106110
const validateRmOptionsPromise = promisify(validateRmOptions);
107111

@@ -228,7 +232,18 @@ class FileHandle extends EventEmitterMixin(JSTransferable) {
228232
if (this[kLocked])
229233
throw new ERR_INVALID_STATE('The FileHandle is locked');
230234
this[kLocked] = true;
231-
return newReadableStreamFromStreamBase(this[kHandle]);
235+
236+
const readable = newReadableStreamFromStreamBase(
237+
this[kHandle],
238+
undefined,
239+
{ ondone: () => this[kUnref]() });
240+
241+
this[kRef]();
242+
this.once('close', () => {
243+
readableStreamCancel(readable);
244+
});
245+
246+
return readable;
232247
}
233248

234249
[kTransfer]() {

lib/internal/webstreams/adapters.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,12 +859,20 @@ function newWritableStreamFromStreamBase(streamBase, strategy) {
859859
* @param {QueuingStrategy} strategy
860860
* @returns {ReadableStream}
861861
*/
862-
function newReadableStreamFromStreamBase(streamBase, strategy) {
862+
function newReadableStreamFromStreamBase(streamBase, strategy, options = {}) {
863863
validateObject(streamBase, 'streamBase');
864+
validateObject(options, 'options');
865+
866+
const {
867+
ondone = () => {},
868+
} = options;
864869

865870
if (typeof streamBase.onread === 'function')
866871
throw new ERR_INVALID_STATE('StreamBase already has a consumer');
867872

873+
if (typeof ondone !== 'function')
874+
throw new ERR_INVALID_ARG_TYPE('options.ondone', 'Function', ondone);
875+
868876
let controller;
869877

870878
streamBase.onread = (arrayBuffer) => {
@@ -877,6 +885,11 @@ function newReadableStreamFromStreamBase(streamBase, strategy) {
877885
if (nread === UV_EOF) {
878886
controller.close();
879887
streamBase.readStop();
888+
try {
889+
ondone();
890+
} catch (error) {
891+
controller.error(error);
892+
}
880893
return;
881894
}
882895

@@ -899,6 +912,12 @@ function newReadableStreamFromStreamBase(streamBase, strategy) {
899912

900913
cancel() {
901914
const promise = createDeferredPromise();
915+
try {
916+
ondone();
917+
} catch (error) {
918+
promise.reject(error);
919+
return promise.promise;
920+
}
902921
const req = new ShutdownWrap();
903922
req.oncomplete = () => promise.resolve();
904923
const err = streamBase.shutdown(req);

test/parallel/test-filehandle-readablestream.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313

1414
const check = readFileSync(__filename, { encoding: 'utf8' });
1515

16+
// Make sure the ReadableStream works...
1617
(async () => {
1718
const dec = new TextDecoder();
1819
const file = await open(__filename);
@@ -29,6 +30,8 @@ const check = readFileSync(__filename, { encoding: 'utf8' });
2930
await file.close();
3031
})().then(common.mustCall());
3132

33+
// Make sure that acquiring a ReadableStream fails if the
34+
// FileHandle is already closed.
3235
(async () => {
3336
const file = await open(__filename);
3437
await file.close();
@@ -38,6 +41,8 @@ const check = readFileSync(__filename, { encoding: 'utf8' });
3841
});
3942
})().then(common.mustCall());
4043

44+
// Make sure that acquiring a ReadableStream fails if the
45+
// FileHandle is already closing.
4146
(async () => {
4247
const file = await open(__filename);
4348
file.close();
@@ -46,3 +51,37 @@ const check = readFileSync(__filename, { encoding: 'utf8' });
4651
code: 'ERR_INVALID_STATE',
4752
});
4853
})().then(common.mustCall());
54+
55+
// Make sure the ReadableStream is closed when the underlying
56+
// FileHandle is closed.
57+
(async () => {
58+
const file = await open(__filename);
59+
const readable = file.readableWebStream();
60+
const reader = readable.getReader();
61+
file.close();
62+
await reader.closed;
63+
})().then(common.mustCall());
64+
65+
// Make sure the ReadableStream is closed when the underlying
66+
// FileHandle is closed.
67+
(async () => {
68+
const file = await open(__filename);
69+
const readable = file.readableWebStream();
70+
file.close();
71+
const reader = readable.getReader();
72+
await reader.closed;
73+
})().then(common.mustCall());
74+
75+
// Make sure that the FileHandle is properly marked "in use"
76+
// when a ReadableStream has been acquired for it.
77+
(async () => {
78+
const file = await open(__filename);
79+
file.readableWebStream();
80+
const mc = new MessageChannel();
81+
mc.port1.onmessage = common.mustNotCall();
82+
assert.throws(() => mc.port2.postMessage(file, [file]), {
83+
code: 25 // DataCloneError
84+
});
85+
mc.port1.close();
86+
await file.close();
87+
})().then(common.mustCall());

0 commit comments

Comments
 (0)